##// END OF EJS Templates
Removed user and settings mode. Added settings manager to manage settings and keep them in the session (or any other backend like cookie in the future
neko259 -
r728:a5c2ce32 2.0-dev
parent child Browse files
Show More
@@ -0,0 +1,106 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 class SettingsManager:
19
20 def __init__(self, session):
21 self.session = session
22
23 def get_theme(self):
24 theme = self.get_setting(SETTING_THEME)
25 if not theme:
26 theme = DEFAULT_THEME
27 self.set_setting(SETTING_THEME, theme)
28
29 return theme
30
31 def set_theme(self, theme):
32 self.set_setting(SETTING_THEME, theme)
33
34 def has_permission(self, permission):
35 permissions = self.get_setting(SETTING_PERMISSIONS)
36 if permissions:
37 return permission in permissions
38 else:
39 return False
40
41 def get_setting(self, setting):
42 if setting in self.session:
43 return self.session[setting]
44 else:
45 return None
46
47 def set_setting(self, setting, value):
48 self.session[setting] = value
49
50 def add_permission(self, permission):
51 permissions = self.get_setting(SETTING_PERMISSIONS)
52 if not permissions:
53 permissions = [permission]
54 else:
55 permissions += permission
56 self.set_setting(SETTING_PERMISSIONS, permissions)
57
58 def get_fav_tags(self):
59 tag_names = self.get_setting(SETTING_FAVORITE_TAGS)
60 tags = []
61 if tag_names:
62 for tag_name in tag_names:
63 tag = get_object_or_404(Tag, name=tag_name)
64 tags.append(tag)
65
66 return tags
67
68 def add_fav_tag(self, tag):
69 tags = self.get_setting(SETTING_FAVORITE_TAGS)
70 if not tags:
71 tags = [tag.name]
72 else:
73 if not tag.name in tags:
74 tags.append(tag.name)
75 self.set_setting(SETTING_FAVORITE_TAGS, tags)
76
77 def del_fav_tag(self, tag):
78 tags = self.get_setting(SETTING_FAVORITE_TAGS)
79 if tag.name in tags:
80 tags.remove(tag.name)
81 self.set_setting(SETTING_FAVORITE_TAGS, tags)
82
83 def get_hidden_tags(self):
84 tag_names = self.get_setting(SETTING_HIDDEN_TAGS)
85 tags = []
86 if tag_names:
87 for tag_name in tag_names:
88 tag = get_object_or_404(Tag, name=tag_name)
89 tags.append(tag)
90
91 return tags
92
93 def add_hidden_tag(self, tag):
94 tags = self.get_setting(SETTING_HIDDEN_TAGS)
95 if not tags:
96 tags = [tag.name]
97 else:
98 if not tag.name in tags:
99 tags.append(tag.name)
100 self.set_setting(SETTING_HIDDEN_TAGS, tags)
101
102 def del_hidden_tag(self, tag):
103 tags = self.get_setting(SETTING_HIDDEN_TAGS)
104 if tag.name in tags:
105 tags.remove(tag.name)
106 self.set_setting(SETTING_HIDDEN_TAGS, tags)
@@ -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
@@ -1,40 +1,33 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):
6
6
7 list_display = ('id', 'title', 'text')
7 list_display = ('id', 'title', 'text')
8 list_filter = ('pub_time', 'thread_new')
8 list_filter = ('pub_time', 'thread_new')
9 search_fields = ('id', 'title', 'text')
9 search_fields = ('id', 'title', 'text')
10
10
11
11
12 class TagAdmin(admin.ModelAdmin):
12 class TagAdmin(admin.ModelAdmin):
13
13
14 list_display = ('name', 'linked')
14 list_display = ('name', 'linked')
15 list_filter = ('linked',)
15 list_filter = ('linked',)
16
16
17
17
18 class UserAdmin(admin.ModelAdmin):
19
20 list_display = ('user_id', 'rank')
21 search_fields = ('user_id',)
22
23
24 class ThreadAdmin(admin.ModelAdmin):
18 class ThreadAdmin(admin.ModelAdmin):
25
19
26 def title(self, obj):
20 def title(self, obj):
27 return obj.get_opening_post().title
21 return obj.get_opening_post().title
28
22
29 def reply_count(self, obj):
23 def reply_count(self, obj):
30 return obj.get_reply_count()
24 return obj.get_reply_count()
31
25
32 list_display = ('id', 'title', 'reply_count', 'archived')
26 list_display = ('id', 'title', 'reply_count', 'archived')
33 list_filter = ('bump_time', 'archived')
27 list_filter = ('bump_time', 'archived')
34 search_fields = ('id', 'title')
28 search_fields = ('id', 'title')
35
29
36 admin.site.register(Post, PostAdmin)
30 admin.site.register(Post, PostAdmin)
37 admin.site.register(Tag, TagAdmin)
31 admin.site.register(Tag, TagAdmin)
38 admin.site.register(User, UserAdmin)
39 admin.site.register(Ban)
32 admin.site.register(Ban)
40 admin.site.register(Thread, ThreadAdmin)
33 admin.site.register(Thread, ThreadAdmin)
@@ -1,39 +1,37 b''
1 from boards.abstracts.settingsmanager import SettingsManager, \
2 PERMISSION_MODERATE
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'
9 CONTEXT_MODERATOR = 'moderator'
11 CONTEXT_MODERATOR = 'moderator'
10 CONTEXT_THEME_CSS = 'theme_css'
12 CONTEXT_THEME_CSS = 'theme_css'
11 CONTEXT_THEME = 'theme'
13 CONTEXT_THEME = 'theme'
12 CONTEXT_PPD = 'posts_per_day'
14 CONTEXT_PPD = 'posts_per_day'
13 CONTEXT_TAGS = 'tags'
15 CONTEXT_TAGS = 'tags'
14 CONTEXT_USER = 'user'
16 CONTEXT_USER = 'user'
15
17
16
18
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 = SettingsManager(request.session)
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
38
36
39 return context No newline at end of file
37 return context
@@ -1,357 +1,308 b''
1 import re
1 import re
2 import time
2 import time
3 import hashlib
3 import hashlib
4
4
5 from captcha.fields import CaptchaField
5 from captcha.fields import CaptchaField
6 from django import forms
6 from django import forms
7 from django.forms.util import ErrorList
7 from django.forms.util import ErrorList
8 from django.utils.translation import ugettext_lazy as _
8 from django.utils.translation import ugettext_lazy as _
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
16
16
17 VETERAN_POSTING_DELAY = 5
17 VETERAN_POSTING_DELAY = 5
18
18
19 ATTRIBUTE_PLACEHOLDER = 'placeholder'
19 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. You can reply to message >>123 like
24 this. 2 new lines are required to start new paragraph.''')
24 this. 2 new lines are required to start new paragraph.''')
25 TAGS_PLACEHOLDER = _('tag1 several_words_tag')
25 TAGS_PLACEHOLDER = _('tag1 several_words_tag')
26
26
27 ERROR_IMAGE_DUPLICATE = _('Such image was already posted')
27 ERROR_IMAGE_DUPLICATE = _('Such image was already posted')
28
28
29 LABEL_TITLE = _('Title')
29 LABEL_TITLE = _('Title')
30 LABEL_TEXT = _('Text')
30 LABEL_TEXT = _('Text')
31 LABEL_TAG = _('Tag')
31 LABEL_TAG = _('Tag')
32 LABEL_SEARCH = _('Search')
32 LABEL_SEARCH = _('Search')
33
33
34 TAG_MAX_LENGTH = 20
34 TAG_MAX_LENGTH = 20
35
35
36 REGEX_TAG = ur'^[\w\d]+$'
36 REGEX_TAG = ur'^[\w\d]+$'
37
37
38
38
39 class FormatPanel(forms.Textarea):
39 class FormatPanel(forms.Textarea):
40 def render(self, name, value, attrs=None):
40 def render(self, name, value, attrs=None):
41 output = '<div id="mark-panel">'
41 output = '<div id="mark-panel">'
42 for formatter in formatters:
42 for formatter in formatters:
43 output += u'<span class="mark_btn"' + \
43 output += u'<span class="mark_btn"' + \
44 u' onClick="addMarkToMsg(\'' + formatter.format_left + \
44 u' onClick="addMarkToMsg(\'' + formatter.format_left + \
45 '\', \'' + formatter.format_right + '\')">' + \
45 '\', \'' + formatter.format_right + '\')">' + \
46 formatter.preview_left + formatter.name + \
46 formatter.preview_left + formatter.name + \
47 formatter.preview_right + u'</span>'
47 formatter.preview_right + u'</span>'
48
48
49 output += '</div>'
49 output += '</div>'
50 output += super(FormatPanel, self).render(name, value, attrs=None)
50 output += super(FormatPanel, self).render(name, value, attrs=None)
51
51
52 return output
52 return output
53
53
54
54
55 class PlainErrorList(ErrorList):
55 class PlainErrorList(ErrorList):
56 def __unicode__(self):
56 def __unicode__(self):
57 return self.as_text()
57 return self.as_text()
58
58
59 def as_text(self):
59 def as_text(self):
60 return ''.join([u'(!) %s ' % e for e in self])
60 return ''.join([u'(!) %s ' % e for e in self])
61
61
62
62
63 class NeboardForm(forms.Form):
63 class NeboardForm(forms.Form):
64
64
65 def as_div(self):
65 def as_div(self):
66 """
66 """
67 Returns this form rendered as HTML <as_div>s.
67 Returns this form rendered as HTML <as_div>s.
68 """
68 """
69
69
70 return self._html_output(
70 return self._html_output(
71 # TODO Do not show hidden rows in the list here
71 # TODO Do not show hidden rows in the list here
72 normal_row='<div class="form-row"><div class="form-label">'
72 normal_row='<div class="form-row"><div class="form-label">'
73 '%(label)s'
73 '%(label)s'
74 '</div></div>'
74 '</div></div>'
75 '<div class="form-row"><div class="form-input">'
75 '<div class="form-row"><div class="form-input">'
76 '%(field)s'
76 '%(field)s'
77 '</div></div>'
77 '</div></div>'
78 '<div class="form-row">'
78 '<div class="form-row">'
79 '%(help_text)s'
79 '%(help_text)s'
80 '</div>',
80 '</div>',
81 error_row='<div class="form-row">'
81 error_row='<div class="form-row">'
82 '<div class="form-label"></div>'
82 '<div class="form-label"></div>'
83 '<div class="form-errors">%s</div>'
83 '<div class="form-errors">%s</div>'
84 '</div>',
84 '</div>',
85 row_ender='</div>',
85 row_ender='</div>',
86 help_text_html='%s',
86 help_text_html='%s',
87 errors_on_separate_row=True)
87 errors_on_separate_row=True)
88
88
89 def as_json_errors(self):
89 def as_json_errors(self):
90 errors = []
90 errors = []
91
91
92 for name, field in self.fields.items():
92 for name, field in self.fields.items():
93 if self[name].errors:
93 if self[name].errors:
94 errors.append({
94 errors.append({
95 'field': name,
95 'field': name,
96 'errors': self[name].errors.as_text(),
96 'errors': self[name].errors.as_text(),
97 })
97 })
98
98
99 return errors
99 return errors
100
100
101
101
102 class PostForm(NeboardForm):
102 class PostForm(NeboardForm):
103
103
104 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
104 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
105 label=LABEL_TITLE)
105 label=LABEL_TITLE)
106 text = forms.CharField(
106 text = forms.CharField(
107 widget=FormatPanel(attrs={ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER}),
107 widget=FormatPanel(attrs={ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER}),
108 required=False, label=LABEL_TEXT)
108 required=False, label=LABEL_TEXT)
109 image = forms.ImageField(required=False, label=_('Image'),
109 image = forms.ImageField(required=False, label=_('Image'),
110 widget=forms.ClearableFileInput(
110 widget=forms.ClearableFileInput(
111 attrs={'accept': 'image/*'}))
111 attrs={'accept': 'image/*'}))
112
112
113 # This field is for spam prevention only
113 # This field is for spam prevention only
114 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
114 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
115 widget=forms.TextInput(attrs={
115 widget=forms.TextInput(attrs={
116 'class': 'form-email'}))
116 'class': 'form-email'}))
117
117
118 session = None
118 session = None
119 need_to_ban = False
119 need_to_ban = False
120
120
121 def clean_title(self):
121 def clean_title(self):
122 title = self.cleaned_data['title']
122 title = self.cleaned_data['title']
123 if title:
123 if title:
124 if len(title) > TITLE_MAX_LENGTH:
124 if len(title) > TITLE_MAX_LENGTH:
125 raise forms.ValidationError(_('Title must have less than %s '
125 raise forms.ValidationError(_('Title must have less than %s '
126 'characters') %
126 'characters') %
127 str(TITLE_MAX_LENGTH))
127 str(TITLE_MAX_LENGTH))
128 return title
128 return title
129
129
130 def clean_text(self):
130 def clean_text(self):
131 text = self.cleaned_data['text'].strip()
131 text = self.cleaned_data['text'].strip()
132 if text:
132 if text:
133 if len(text) > board_settings.MAX_TEXT_LENGTH:
133 if len(text) > board_settings.MAX_TEXT_LENGTH:
134 raise forms.ValidationError(_('Text must have less than %s '
134 raise forms.ValidationError(_('Text must have less than %s '
135 'characters') %
135 'characters') %
136 str(board_settings
136 str(board_settings
137 .MAX_TEXT_LENGTH))
137 .MAX_TEXT_LENGTH))
138 return text
138 return text
139
139
140 def clean_image(self):
140 def clean_image(self):
141 image = self.cleaned_data['image']
141 image = self.cleaned_data['image']
142 if image:
142 if image:
143 if image.size > board_settings.MAX_IMAGE_SIZE:
143 if image.size > board_settings.MAX_IMAGE_SIZE:
144 raise forms.ValidationError(
144 raise forms.ValidationError(
145 _('Image must be less than %s bytes')
145 _('Image must be less than %s bytes')
146 % str(board_settings.MAX_IMAGE_SIZE))
146 % str(board_settings.MAX_IMAGE_SIZE))
147
147
148 md5 = hashlib.md5()
148 md5 = hashlib.md5()
149 for chunk in image.chunks():
149 for chunk in image.chunks():
150 md5.update(chunk)
150 md5.update(chunk)
151 image_hash = md5.hexdigest()
151 image_hash = md5.hexdigest()
152 if PostImage.objects.filter(hash=image_hash).exists():
152 if PostImage.objects.filter(hash=image_hash).exists():
153 raise forms.ValidationError(ERROR_IMAGE_DUPLICATE)
153 raise forms.ValidationError(ERROR_IMAGE_DUPLICATE)
154
154
155 return image
155 return image
156
156
157 def clean(self):
157 def clean(self):
158 cleaned_data = super(PostForm, self).clean()
158 cleaned_data = super(PostForm, self).clean()
159
159
160 if not self.session:
160 if not self.session:
161 raise forms.ValidationError('Humans have sessions')
161 raise forms.ValidationError('Humans have sessions')
162
162
163 if cleaned_data['email']:
163 if cleaned_data['email']:
164 self.need_to_ban = True
164 self.need_to_ban = True
165 raise forms.ValidationError('A human cannot enter a hidden field')
165 raise forms.ValidationError('A human cannot enter a hidden field')
166
166
167 if not self.errors:
167 if not self.errors:
168 self._clean_text_image()
168 self._clean_text_image()
169
169
170 if not self.errors and self.session:
170 if not self.errors and self.session:
171 self._validate_posting_speed()
171 self._validate_posting_speed()
172
172
173 return cleaned_data
173 return cleaned_data
174
174
175 def _clean_text_image(self):
175 def _clean_text_image(self):
176 text = self.cleaned_data.get('text')
176 text = self.cleaned_data.get('text')
177 image = self.cleaned_data.get('image')
177 image = self.cleaned_data.get('image')
178
178
179 if (not text) and (not image):
179 if (not text) and (not image):
180 error_message = _('Either text or image must be entered.')
180 error_message = _('Either text or image must be entered.')
181 self._errors['text'] = self.error_class([error_message])
181 self._errors['text'] = self.error_class([error_message])
182
182
183 def _validate_posting_speed(self):
183 def _validate_posting_speed(self):
184 can_post = True
184 can_post = True
185
185
186 # TODO Remove this, it's only for test
186 # TODO Remove this, it's only for test
187 if not 'user_id' in self.session:
187 if not 'user_id' in self.session:
188 return
188 return
189
189
190 user = User.objects.get(id=self.session['user_id'])
190 posting_delay = settings.POSTING_DELAY
191 if user.is_veteran():
192 posting_delay = VETERAN_POSTING_DELAY
193 else:
194 posting_delay = settings.POSTING_DELAY
195
191
196 if board_settings.LIMIT_POSTING_SPEED and LAST_POST_TIME in \
192 if board_settings.LIMIT_POSTING_SPEED and LAST_POST_TIME in \
197 self.session:
193 self.session:
198 now = time.time()
194 now = time.time()
199 last_post_time = self.session[LAST_POST_TIME]
195 last_post_time = self.session[LAST_POST_TIME]
200
196
201 current_delay = int(now - last_post_time)
197 current_delay = int(now - last_post_time)
202
198
203 if current_delay < posting_delay:
199 if current_delay < posting_delay:
204 error_message = _('Wait %s seconds after last posting') % str(
200 error_message = _('Wait %s seconds after last posting') % str(
205 posting_delay - current_delay)
201 posting_delay - current_delay)
206 self._errors['text'] = self.error_class([error_message])
202 self._errors['text'] = self.error_class([error_message])
207
203
208 can_post = False
204 can_post = False
209
205
210 if can_post:
206 if can_post:
211 self.session[LAST_POST_TIME] = time.time()
207 self.session[LAST_POST_TIME] = time.time()
212
208
213
209
214 class ThreadForm(PostForm):
210 class ThreadForm(PostForm):
215
211
216 regex_tags = re.compile(ur'^[\w\s\d]+$', re.UNICODE)
212 regex_tags = re.compile(ur'^[\w\s\d]+$', re.UNICODE)
217
213
218 tags = forms.CharField(
214 tags = forms.CharField(
219 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
215 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
220 max_length=100, label=_('Tags'), required=True)
216 max_length=100, label=_('Tags'), required=True)
221
217
222 def clean_tags(self):
218 def clean_tags(self):
223 tags = self.cleaned_data['tags'].strip()
219 tags = self.cleaned_data['tags'].strip()
224
220
225 if not tags or not self.regex_tags.match(tags):
221 if not tags or not self.regex_tags.match(tags):
226 raise forms.ValidationError(
222 raise forms.ValidationError(
227 _('Inappropriate characters in tags.'))
223 _('Inappropriate characters in tags.'))
228
224
229 return tags
225 return tags
230
226
231 def clean(self):
227 def clean(self):
232 cleaned_data = super(ThreadForm, self).clean()
228 cleaned_data = super(ThreadForm, self).clean()
233
229
234 return cleaned_data
230 return cleaned_data
235
231
236
232
237 class PostCaptchaForm(PostForm):
233 class PostCaptchaForm(PostForm):
238 captcha = CaptchaField()
234 captcha = CaptchaField()
239
235
240 def __init__(self, *args, **kwargs):
236 def __init__(self, *args, **kwargs):
241 self.request = kwargs['request']
237 self.request = kwargs['request']
242 del kwargs['request']
238 del kwargs['request']
243
239
244 super(PostCaptchaForm, self).__init__(*args, **kwargs)
240 super(PostCaptchaForm, self).__init__(*args, **kwargs)
245
241
246 def clean(self):
242 def clean(self):
247 cleaned_data = super(PostCaptchaForm, self).clean()
243 cleaned_data = super(PostCaptchaForm, self).clean()
248
244
249 success = self.is_valid()
245 success = self.is_valid()
250 utils.update_captcha_access(self.request, success)
246 utils.update_captcha_access(self.request, success)
251
247
252 if success:
248 if success:
253 return cleaned_data
249 return cleaned_data
254 else:
250 else:
255 raise forms.ValidationError(_("Captcha validation failed"))
251 raise forms.ValidationError(_("Captcha validation failed"))
256
252
257
253
258 class ThreadCaptchaForm(ThreadForm):
254 class ThreadCaptchaForm(ThreadForm):
259 captcha = CaptchaField()
255 captcha = CaptchaField()
260
256
261 def __init__(self, *args, **kwargs):
257 def __init__(self, *args, **kwargs):
262 self.request = kwargs['request']
258 self.request = kwargs['request']
263 del kwargs['request']
259 del kwargs['request']
264
260
265 super(ThreadCaptchaForm, self).__init__(*args, **kwargs)
261 super(ThreadCaptchaForm, self).__init__(*args, **kwargs)
266
262
267 def clean(self):
263 def clean(self):
268 cleaned_data = super(ThreadCaptchaForm, self).clean()
264 cleaned_data = super(ThreadCaptchaForm, self).clean()
269
265
270 success = self.is_valid()
266 success = self.is_valid()
271 utils.update_captcha_access(self.request, success)
267 utils.update_captcha_access(self.request, success)
272
268
273 if success:
269 if success:
274 return cleaned_data
270 return cleaned_data
275 else:
271 else:
276 raise forms.ValidationError(_("Captcha validation failed"))
272 raise forms.ValidationError(_("Captcha validation failed"))
277
273
278
274
279 class SettingsForm(NeboardForm):
275 class SettingsForm(NeboardForm):
280
276
281 theme = forms.ChoiceField(choices=settings.THEMES,
277 theme = forms.ChoiceField(choices=settings.THEMES,
282 label=_('Theme'))
278 label=_('Theme'))
283
279
284
280
285 class ModeratorSettingsForm(SettingsForm):
281 class ModeratorSettingsForm(SettingsForm):
286
282
287 moderate = forms.BooleanField(required=False, label=_('Enable moderation '
283 moderate = forms.BooleanField(required=False, label=_('Enable moderation '
288 'panel'))
284 'panel'))
289
285
290
286
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):
287 class AddTagForm(NeboardForm):
337
288
338 tag = forms.CharField(max_length=TAG_MAX_LENGTH, label=LABEL_TAG)
289 tag = forms.CharField(max_length=TAG_MAX_LENGTH, label=LABEL_TAG)
339 method = forms.CharField(widget=forms.HiddenInput(), initial='add_tag')
290 method = forms.CharField(widget=forms.HiddenInput(), initial='add_tag')
340
291
341 def clean_tag(self):
292 def clean_tag(self):
342 tag = self.cleaned_data['tag']
293 tag = self.cleaned_data['tag']
343
294
344 regex_tag = re.compile(REGEX_TAG, re.UNICODE)
295 regex_tag = re.compile(REGEX_TAG, re.UNICODE)
345 if not regex_tag.match(tag):
296 if not regex_tag.match(tag):
346 raise forms.ValidationError(_('Inappropriate characters in tags.'))
297 raise forms.ValidationError(_('Inappropriate characters in tags.'))
347
298
348 return tag
299 return tag
349
300
350 def clean(self):
301 def clean(self):
351 cleaned_data = super(AddTagForm, self).clean()
302 cleaned_data = super(AddTagForm, self).clean()
352
303
353 return cleaned_data
304 return cleaned_data
354
305
355
306
356 class SearchForm(NeboardForm):
307 class SearchForm(NeboardForm):
357 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False) No newline at end of file
308 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
@@ -1,16 +1,16 b''
1 from django.core.management import BaseCommand
1 from django.core.management import BaseCommand
2 from django.db import transaction
2 from django.db import transaction
3
3
4 from boards.models import Post
4 from boards.models import Post
5 from boards.models.post import NO_IP
5 from boards.models.post import NO_IP
6
6
7
7
8 __author__ = 'neko259'
8 __author__ = 'neko259'
9
9
10
10
11 class Command(BaseCommand):
11 class Command(BaseCommand):
12 help = 'Removes user and IP data from all posts'
12 help = 'Removes user and IP data from all posts'
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,9 +1,7 b''
1 __author__ = 'neko259'
1 __author__ = 'neko259'
2
2
3 from boards.models.image import PostImage
3 from boards.models.image import PostImage
4 from boards.models.thread import Thread
4 from boards.models.thread import Thread
5 from boards.models.post import Post
5 from boards.models.post import Post
6 from boards.models.tag import Tag
6 from boards.models.tag import Tag
7 from boards.models.user import Ban
7 from boards.models.user import Ban
8 from boards.models.user import Setting
9 from boards.models.user import User
@@ -1,351 +1,349 b''
1 from datetime import datetime, timedelta, date
1 from datetime import datetime, timedelta, date
2 from datetime import time as dtime
2 from datetime import time as dtime
3 import logging
3 import logging
4 import re
4 import re
5
5
6 from django.core.cache import cache
6 from django.core.cache import cache
7 from django.core.urlresolvers import reverse
7 from django.core.urlresolvers import reverse
8 from django.db import models, transaction
8 from django.db import models, transaction
9 from django.template.loader import render_to_string
9 from django.template.loader import render_to_string
10 from django.utils import timezone
10 from django.utils import timezone
11 from markupfield.fields import MarkupField
11 from markupfield.fields import MarkupField
12
12
13 from boards.models import PostImage
13 from boards.models import PostImage
14 from boards.models.base import Viewable
14 from boards.models.base import Viewable
15 from boards.models.thread import Thread
15 from boards.models.thread import Thread
16
16
17
17
18 APP_LABEL_BOARDS = 'boards'
18 APP_LABEL_BOARDS = 'boards'
19
19
20 CACHE_KEY_PPD = 'ppd'
20 CACHE_KEY_PPD = 'ppd'
21 CACHE_KEY_POST_URL = 'post_url'
21 CACHE_KEY_POST_URL = 'post_url'
22
22
23 POSTS_PER_DAY_RANGE = range(7)
23 POSTS_PER_DAY_RANGE = range(7)
24
24
25 BAN_REASON_AUTO = 'Auto'
25 BAN_REASON_AUTO = 'Auto'
26
26
27 IMAGE_THUMB_SIZE = (200, 150)
27 IMAGE_THUMB_SIZE = (200, 150)
28
28
29 TITLE_MAX_LENGTH = 200
29 TITLE_MAX_LENGTH = 200
30
30
31 DEFAULT_MARKUP_TYPE = 'markdown'
31 DEFAULT_MARKUP_TYPE = 'markdown'
32
32
33 # TODO This should be removed
33 # TODO This should be removed
34 NO_IP = '0.0.0.0'
34 NO_IP = '0.0.0.0'
35
35
36 # TODO Real user agent should be saved instead of this
36 # TODO Real user agent should be saved instead of this
37 UNKNOWN_UA = ''
37 UNKNOWN_UA = ''
38
38
39 SETTING_MODERATE = "moderate"
39 SETTING_MODERATE = "moderate"
40
40
41 REGEX_REPLY = re.compile('>>(\d+)')
41 REGEX_REPLY = re.compile('>>(\d+)')
42
42
43 logger = logging.getLogger(__name__)
43 logger = logging.getLogger(__name__)
44
44
45
45
46 class PostManager(models.Manager):
46 class PostManager(models.Manager):
47
47
48 def create_post(self, title, text, image=None, thread=None,
48 def create_post(self, title, text, image=None, thread=None, ip=NO_IP,
49 ip=NO_IP, tags=None, user=None):
49 tags=None):
50 """
50 """
51 Creates new post
51 Creates new post
52 """
52 """
53
53
54 posting_time = timezone.now()
54 posting_time = timezone.now()
55 if not thread:
55 if not thread:
56 thread = Thread.objects.create(bump_time=posting_time,
56 thread = Thread.objects.create(bump_time=posting_time,
57 last_edit_time=posting_time)
57 last_edit_time=posting_time)
58 new_thread = True
58 new_thread = True
59 else:
59 else:
60 thread.bump()
60 thread.bump()
61 thread.last_edit_time = posting_time
61 thread.last_edit_time = posting_time
62 thread.save()
62 thread.save()
63 new_thread = False
63 new_thread = False
64
64
65 post = self.create(title=title,
65 post = self.create(title=title,
66 text=text,
66 text=text,
67 pub_time=posting_time,
67 pub_time=posting_time,
68 thread_new=thread,
68 thread_new=thread,
69 poster_ip=ip,
69 poster_ip=ip,
70 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
70 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
71 # last!
71 # last!
72 last_edit_time=posting_time,
72 last_edit_time=posting_time)
73 user=user)
74
73
75 if image:
74 if image:
76 post_image = PostImage.objects.create(image=image)
75 post_image = PostImage.objects.create(image=image)
77 post.images.add(post_image)
76 post.images.add(post_image)
78 logger.info('Created image #%d for post #%d' % (post_image.id,
77 logger.info('Created image #%d for post #%d' % (post_image.id,
79 post.id))
78 post.id))
80
79
81 thread.replies.add(post)
80 thread.replies.add(post)
82 if tags:
81 if tags:
83 linked_tags = []
82 linked_tags = []
84 for tag in tags:
83 for tag in tags:
85 tag_linked_tags = tag.get_linked_tags()
84 tag_linked_tags = tag.get_linked_tags()
86 if len(tag_linked_tags) > 0:
85 if len(tag_linked_tags) > 0:
87 linked_tags.extend(tag_linked_tags)
86 linked_tags.extend(tag_linked_tags)
88
87
89 tags.extend(linked_tags)
88 tags.extend(linked_tags)
90 map(thread.add_tag, tags)
89 map(thread.add_tag, tags)
91
90
92 if new_thread:
91 if new_thread:
93 Thread.objects.process_oldest_threads()
92 Thread.objects.process_oldest_threads()
94 self.connect_replies(post)
93 self.connect_replies(post)
95
94
96 logger.info('Created post #%d' % post.id)
95 logger.info('Created post #%d' % post.id)
97
96
98 return post
97 return post
99
98
100 def delete_post(self, post):
99 def delete_post(self, post):
101 """
100 """
102 Deletes post and update or delete its thread
101 Deletes post and update or delete its thread
103 """
102 """
104
103
105 post_id = post.id
104 post_id = post.id
106
105
107 thread = post.get_thread()
106 thread = post.get_thread()
108
107
109 if post.is_opening():
108 if post.is_opening():
110 thread.delete()
109 thread.delete()
111 else:
110 else:
112 thread.last_edit_time = timezone.now()
111 thread.last_edit_time = timezone.now()
113 thread.save()
112 thread.save()
114
113
115 post.delete()
114 post.delete()
116
115
117 logger.info('Deleted post #%d' % post_id)
116 logger.info('Deleted post #%d' % post_id)
118
117
119 def delete_posts_by_ip(self, ip):
118 def delete_posts_by_ip(self, ip):
120 """
119 """
121 Deletes all posts of the author with same IP
120 Deletes all posts of the author with same IP
122 """
121 """
123
122
124 posts = self.filter(poster_ip=ip)
123 posts = self.filter(poster_ip=ip)
125 map(self.delete_post, posts)
124 map(self.delete_post, posts)
126
125
127 def connect_replies(self, post):
126 def connect_replies(self, post):
128 """
127 """
129 Connects replies to a post to show them as a reflink map
128 Connects replies to a post to show them as a reflink map
130 """
129 """
131
130
132 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
131 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
133 post_id = reply_number.group(1)
132 post_id = reply_number.group(1)
134 ref_post = self.filter(id=post_id)
133 ref_post = self.filter(id=post_id)
135 if ref_post.count() > 0:
134 if ref_post.count() > 0:
136 referenced_post = ref_post[0]
135 referenced_post = ref_post[0]
137 referenced_post.referenced_posts.add(post)
136 referenced_post.referenced_posts.add(post)
138 referenced_post.last_edit_time = post.pub_time
137 referenced_post.last_edit_time = post.pub_time
139 referenced_post.build_refmap()
138 referenced_post.build_refmap()
140 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
139 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
141
140
142 referenced_thread = referenced_post.get_thread()
141 referenced_thread = referenced_post.get_thread()
143 referenced_thread.last_edit_time = post.pub_time
142 referenced_thread.last_edit_time = post.pub_time
144 referenced_thread.save(update_fields=['last_edit_time'])
143 referenced_thread.save(update_fields=['last_edit_time'])
145
144
146 def get_posts_per_day(self):
145 def get_posts_per_day(self):
147 """
146 """
148 Gets average count of posts per day for the last 7 days
147 Gets average count of posts per day for the last 7 days
149 """
148 """
150
149
151 today = date.today()
150 today = date.today()
152 ppd = cache.get(CACHE_KEY_PPD + str(today))
151 ppd = cache.get(CACHE_KEY_PPD + str(today))
153 if ppd:
152 if ppd:
154 return ppd
153 return ppd
155
154
156 posts_per_days = []
155 posts_per_days = []
157 for i in POSTS_PER_DAY_RANGE:
156 for i in POSTS_PER_DAY_RANGE:
158 day_end = today - timedelta(i + 1)
157 day_end = today - timedelta(i + 1)
159 day_start = today - timedelta(i + 2)
158 day_start = today - timedelta(i + 2)
160
159
161 day_time_start = timezone.make_aware(datetime.combine(
160 day_time_start = timezone.make_aware(datetime.combine(
162 day_start, dtime()), timezone.get_current_timezone())
161 day_start, dtime()), timezone.get_current_timezone())
163 day_time_end = timezone.make_aware(datetime.combine(
162 day_time_end = timezone.make_aware(datetime.combine(
164 day_end, dtime()), timezone.get_current_timezone())
163 day_end, dtime()), timezone.get_current_timezone())
165
164
166 posts_per_days.append(float(self.filter(
165 posts_per_days.append(float(self.filter(
167 pub_time__lte=day_time_end,
166 pub_time__lte=day_time_end,
168 pub_time__gte=day_time_start).count()))
167 pub_time__gte=day_time_start).count()))
169
168
170 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
169 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
171 len(posts_per_days))
170 len(posts_per_days))
172 cache.set(CACHE_KEY_PPD + str(today), ppd)
171 cache.set(CACHE_KEY_PPD + str(today), ppd)
173 return ppd
172 return ppd
174
173
175
174
176 class Post(models.Model, Viewable):
175 class Post(models.Model, Viewable):
177 """A post is a message."""
176 """A post is a message."""
178
177
179 objects = PostManager()
178 objects = PostManager()
180
179
181 class Meta:
180 class Meta:
182 app_label = APP_LABEL_BOARDS
181 app_label = APP_LABEL_BOARDS
183 ordering = ('id',)
182 ordering = ('id',)
184
183
185 title = models.CharField(max_length=TITLE_MAX_LENGTH)
184 title = models.CharField(max_length=TITLE_MAX_LENGTH)
186 pub_time = models.DateTimeField()
185 pub_time = models.DateTimeField()
187 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
186 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
188 escape_html=False)
187 escape_html=False)
189
188
190 images = models.ManyToManyField(PostImage, null=True, blank=True,
189 images = models.ManyToManyField(PostImage, null=True, blank=True,
191 related_name='ip+', db_index=True)
190 related_name='ip+', db_index=True)
192
191
193 poster_ip = models.GenericIPAddressField()
192 poster_ip = models.GenericIPAddressField()
194 poster_user_agent = models.TextField()
193 poster_user_agent = models.TextField()
195
194
196 thread_new = models.ForeignKey('Thread', null=True, default=None,
195 thread_new = models.ForeignKey('Thread', null=True, default=None,
197 db_index=True)
196 db_index=True)
198 last_edit_time = models.DateTimeField()
197 last_edit_time = models.DateTimeField()
199 user = models.ForeignKey('User', null=True, default=None, db_index=True)
200
198
201 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
199 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
202 null=True,
200 null=True,
203 blank=True, related_name='rfp+',
201 blank=True, related_name='rfp+',
204 db_index=True)
202 db_index=True)
205 refmap = models.TextField(null=True, blank=True)
203 refmap = models.TextField(null=True, blank=True)
206
204
207 def __unicode__(self):
205 def __unicode__(self):
208 return '#' + str(self.id) + ' ' + self.title + ' (' + \
206 return '#' + str(self.id) + ' ' + self.title + ' (' + \
209 self.text.raw[:50] + ')'
207 self.text.raw[:50] + ')'
210
208
211 def get_title(self):
209 def get_title(self):
212 """
210 """
213 Gets original post title or part of its text.
211 Gets original post title or part of its text.
214 """
212 """
215
213
216 title = self.title
214 title = self.title
217 if not title:
215 if not title:
218 title = self.text.rendered
216 title = self.text.rendered
219
217
220 return title
218 return title
221
219
222 def build_refmap(self):
220 def build_refmap(self):
223 map_string = ''
221 map_string = ''
224
222
225 first = True
223 first = True
226 for refpost in self.referenced_posts.all():
224 for refpost in self.referenced_posts.all():
227 if not first:
225 if not first:
228 map_string += ', '
226 map_string += ', '
229 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(), refpost.id)
227 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(), refpost.id)
230 first = False
228 first = False
231
229
232 self.refmap = map_string
230 self.refmap = map_string
233
231
234 def get_sorted_referenced_posts(self):
232 def get_sorted_referenced_posts(self):
235 return self.refmap
233 return self.refmap
236
234
237 def is_referenced(self):
235 def is_referenced(self):
238 return len(self.refmap) > 0
236 return len(self.refmap) > 0
239
237
240 def is_opening(self):
238 def is_opening(self):
241 """
239 """
242 Checks if this is an opening post or just a reply.
240 Checks if this is an opening post or just a reply.
243 """
241 """
244
242
245 return self.get_thread().get_opening_post_id() == self.id
243 return self.get_thread().get_opening_post_id() == self.id
246
244
247 @transaction.atomic
245 @transaction.atomic
248 def add_tag(self, tag):
246 def add_tag(self, tag):
249 edit_time = timezone.now()
247 edit_time = timezone.now()
250
248
251 thread = self.get_thread()
249 thread = self.get_thread()
252 thread.add_tag(tag)
250 thread.add_tag(tag)
253 self.last_edit_time = edit_time
251 self.last_edit_time = edit_time
254 self.save()
252 self.save()
255
253
256 thread.last_edit_time = edit_time
254 thread.last_edit_time = edit_time
257 thread.save()
255 thread.save()
258
256
259 @transaction.atomic
257 @transaction.atomic
260 def remove_tag(self, tag):
258 def remove_tag(self, tag):
261 edit_time = timezone.now()
259 edit_time = timezone.now()
262
260
263 thread = self.get_thread()
261 thread = self.get_thread()
264 thread.remove_tag(tag)
262 thread.remove_tag(tag)
265 self.last_edit_time = edit_time
263 self.last_edit_time = edit_time
266 self.save()
264 self.save()
267
265
268 thread.last_edit_time = edit_time
266 thread.last_edit_time = edit_time
269 thread.save()
267 thread.save()
270
268
271 def get_url(self, thread=None):
269 def get_url(self, thread=None):
272 """
270 """
273 Gets full url to the post.
271 Gets full url to the post.
274 """
272 """
275
273
276 cache_key = CACHE_KEY_POST_URL + str(self.id)
274 cache_key = CACHE_KEY_POST_URL + str(self.id)
277 link = cache.get(cache_key)
275 link = cache.get(cache_key)
278
276
279 if not link:
277 if not link:
280 if not thread:
278 if not thread:
281 thread = self.get_thread()
279 thread = self.get_thread()
282
280
283 opening_id = thread.get_opening_post_id()
281 opening_id = thread.get_opening_post_id()
284
282
285 if self.id != opening_id:
283 if self.id != opening_id:
286 link = reverse('thread', kwargs={
284 link = reverse('thread', kwargs={
287 'post_id': opening_id}) + '#' + str(self.id)
285 'post_id': opening_id}) + '#' + str(self.id)
288 else:
286 else:
289 link = reverse('thread', kwargs={'post_id': self.id})
287 link = reverse('thread', kwargs={'post_id': self.id})
290
288
291 cache.set(cache_key, link)
289 cache.set(cache_key, link)
292
290
293 return link
291 return link
294
292
295 def get_thread(self):
293 def get_thread(self):
296 """
294 """
297 Gets post's thread.
295 Gets post's thread.
298 """
296 """
299
297
300 return self.thread_new
298 return self.thread_new
301
299
302 def get_referenced_posts(self):
300 def get_referenced_posts(self):
303 return self.referenced_posts.only('id', 'thread_new')
301 return self.referenced_posts.only('id', 'thread_new')
304
302
305 def get_text(self):
303 def get_text(self):
306 return self.text
304 return self.text
307
305
308 def get_view(self, moderator=False, need_open_link=False,
306 def get_view(self, moderator=False, need_open_link=False,
309 truncated=False, *args, **kwargs):
307 truncated=False, *args, **kwargs):
310 if 'is_opening' in kwargs:
308 if 'is_opening' in kwargs:
311 is_opening = kwargs['is_opening']
309 is_opening = kwargs['is_opening']
312 else:
310 else:
313 is_opening = self.is_opening()
311 is_opening = self.is_opening()
314
312
315 if 'thread' in kwargs:
313 if 'thread' in kwargs:
316 thread = kwargs['thread']
314 thread = kwargs['thread']
317 else:
315 else:
318 thread = self.get_thread()
316 thread = self.get_thread()
319
317
320 if 'can_bump' in kwargs:
318 if 'can_bump' in kwargs:
321 can_bump = kwargs['can_bump']
319 can_bump = kwargs['can_bump']
322 else:
320 else:
323 can_bump = thread.can_bump()
321 can_bump = thread.can_bump()
324
322
325 if is_opening:
323 if is_opening:
326 opening_post_id = self.id
324 opening_post_id = self.id
327 else:
325 else:
328 opening_post_id = thread.get_opening_post_id()
326 opening_post_id = thread.get_opening_post_id()
329
327
330 return render_to_string('boards/post.html', {
328 return render_to_string('boards/post.html', {
331 'post': self,
329 'post': self,
332 'moderator': moderator,
330 'moderator': moderator,
333 'is_opening': is_opening,
331 'is_opening': is_opening,
334 'thread': thread,
332 'thread': thread,
335 'bumpable': can_bump,
333 'bumpable': can_bump,
336 'need_open_link': need_open_link,
334 'need_open_link': need_open_link,
337 'truncated': truncated,
335 'truncated': truncated,
338 'opening_post_id': opening_post_id,
336 'opening_post_id': opening_post_id,
339 })
337 })
340
338
341 def get_first_image(self):
339 def get_first_image(self):
342 return self.images.earliest('id')
340 return self.images.earliest('id')
343
341
344 def delete(self, using=None):
342 def delete(self, using=None):
345 """
343 """
346 Delete all post images and the post itself.
344 Delete all post images and the post itself.
347 """
345 """
348
346
349 self.images.all().delete()
347 self.images.all().delete()
350
348
351 super(Post, self).delete(using) No newline at end of file
349 super(Post, self).delete(using)
@@ -1,132 +1,20 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
123 class Meta:
11 class Meta:
124 app_label = 'boards'
12 app_label = 'boards'
125
13
126 ip = models.GenericIPAddressField()
14 ip = models.GenericIPAddressField()
127 reason = models.CharField(default=BAN_REASON_AUTO,
15 reason = models.CharField(default=BAN_REASON_AUTO,
128 max_length=BAN_REASON_MAX_LENGTH)
16 max_length=BAN_REASON_MAX_LENGTH)
129 can_read = models.BooleanField(default=True)
17 can_read = models.BooleanField(default=True)
130
18
131 def __unicode__(self):
19 def __unicode__(self):
132 return self.ip
20 return self.ip
@@ -1,62 +1,61 b''
1 {% load staticfiles %}
1 {% load staticfiles %}
2 {% load i18n %}
2 {% load i18n %}
3 {% load l10n %}
3 {% load l10n %}
4 {% load static from staticfiles %}
4 {% load static from staticfiles %}
5
5
6 <!DOCTYPE html>
6 <!DOCTYPE html>
7 <html>
7 <html>
8 <head>
8 <head>
9 <link rel="stylesheet" type="text/css" href="{% static 'css/base.css' %}" media="all"/>
9 <link rel="stylesheet" type="text/css" href="{% static 'css/base.css' %}" media="all"/>
10 <link rel="stylesheet" type="text/css" href="{% static 'css/3party/highlight.css' %}" media="all"/>
10 <link rel="stylesheet" type="text/css" href="{% static 'css/3party/highlight.css' %}" media="all"/>
11 <link rel="stylesheet" type="text/css" href="{% static theme_css %}" media="all"/>
11 <link rel="stylesheet" type="text/css" href="{% static theme_css %}" media="all"/>
12 <link rel="alternate" type="application/rss+xml" href="rss/" title="{% trans 'Feed' %}"/>
12 <link rel="alternate" type="application/rss+xml" href="rss/" title="{% trans 'Feed' %}"/>
13
13
14 <link rel="icon" type="image/png"
14 <link rel="icon" type="image/png"
15 href="{% static 'favicon.png' %}">
15 href="{% static 'favicon.png' %}">
16
16
17 <meta name="viewport" content="width=device-width, initial-scale=1"/>
17 <meta name="viewport" content="width=device-width, initial-scale=1"/>
18 <meta charset="utf-8"/>
18 <meta charset="utf-8"/>
19
19
20 {% block head %}{% endblock %}
20 {% block head %}{% endblock %}
21 </head>
21 </head>
22 <body>
22 <body>
23 <script src="{% static 'js/jquery-2.0.1.min.js' %}"></script>
23 <script src="{% static 'js/jquery-2.0.1.min.js' %}"></script>
24 <script src="{% static 'js/jquery-ui-1.10.3.custom.min.js' %}"></script>
24 <script src="{% static 'js/jquery-ui-1.10.3.custom.min.js' %}"></script>
25 <script src="{% static 'js/jquery.mousewheel.js' %}"></script>
25 <script src="{% static 'js/jquery.mousewheel.js' %}"></script>
26 <script src="{% url 'js_info_dict' %}"></script>
26 <script src="{% url 'js_info_dict' %}"></script>
27
27
28 <div class="navigation_panel">
28 <div class="navigation_panel">
29 <a class="link" href="{% url 'index' %}">{% trans "All threads" %}</a>
29 <a class="link" href="{% url 'index' %}">{% trans "All threads" %}</a>
30 {% for tag in tags %}
30 {% for tag in tags %}
31 <a class="tag" href="{% url 'tag' tag_name=tag.name %}"
31 <a class="tag" href="{% url 'tag' tag_name=tag.name %}"
32 >#{{ tag.name }}</a>,
32 >#{{ tag.name }}</a>,
33 {% endfor %}
33 {% endfor %}
34 <a href="{% url 'tags' %}" title="{% trans 'Tag management' %}"
34 <a href="{% url 'tags' %}" title="{% trans 'Tag management' %}"
35 >[...]</a>
35 >[...]</a>
36 <a class="link" href="{% url 'settings' %}">{% trans 'Settings' %}</a>
36 <a class="link" href="{% url 'settings' %}">{% trans 'Settings' %}</a>
37 </div>
37 </div>
38
38
39 {% block content %}{% endblock %}
39 {% block content %}{% endblock %}
40
40
41 <script src="{% static 'js/popup.js' %}"></script>
41 <script src="{% static 'js/popup.js' %}"></script>
42 <script src="{% static 'js/image.js' %}"></script>
42 <script src="{% static 'js/image.js' %}"></script>
43 <script src="{% static 'js/3party/highlight.min.js' %}"></script>
43 <script src="{% static 'js/3party/highlight.min.js' %}"></script>
44 <script src="{% static 'js/refpopup.js' %}"></script>
44 <script src="{% static 'js/refpopup.js' %}"></script>
45 <script src="{% static 'js/main.js' %}"></script>
45 <script src="{% static 'js/main.js' %}"></script>
46
46
47 <div class="navigation_panel">
47 <div class="navigation_panel">
48 {% block metapanel %}{% endblock %}
48 {% block metapanel %}{% endblock %}
49 [<a href="{% url "login" %}">{% trans 'Login' %}</a>]
50 [<a href="{% url "search" %}">{% trans 'Search' %}</a>]
49 [<a href="{% url "search" %}">{% trans 'Search' %}</a>]
51 {% with ppd=posts_per_day|floatformat:2 %}
50 {% with ppd=posts_per_day|floatformat:2 %}
52 {% blocktrans %}Speed: {{ ppd }} posts per day{% endblocktrans %}
51 {% blocktrans %}Speed: {{ ppd }} posts per day{% endblocktrans %}
53 {% endwith %}
52 {% endwith %}
54 <a class="link" href="#top">{% trans 'Up' %}</a>
53 <a class="link" href="#top">{% trans 'Up' %}</a>
55 </div>
54 </div>
56
55
57 <div class="footer">
56 <div class="footer">
58 <!-- Put your banners here -->
57 <!-- Put your banners here -->
59 </div>
58 </div>
60
59
61 </body>
60 </body>
62 </html>
61 </html>
@@ -1,197 +1,197 b''
1 {% extends "boards/base.html" %}
1 {% extends "boards/base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4 {% load cache %}
4 {% load cache %}
5 {% load board %}
5 {% load board %}
6 {% load static %}
6 {% load static %}
7
7
8 {% block head %}
8 {% block head %}
9 {% if tag %}
9 {% if tag %}
10 <title>{{ tag.name }} - {{ site_name }}</title>
10 <title>{{ tag.name }} - {{ site_name }}</title>
11 {% else %}
11 {% else %}
12 <title>{{ site_name }}</title>
12 <title>{{ site_name }}</title>
13 {% endif %}
13 {% endif %}
14
14
15 {% if current_page.has_previous %}
15 {% if current_page.has_previous %}
16 <link rel="prev" href="
16 <link rel="prev" href="
17 {% if tag %}
17 {% if tag %}
18 {% url "tag" tag_name=tag page=current_page.previous_page_number %}
18 {% url "tag" tag_name=tag page=current_page.previous_page_number %}
19 {% elif archived %}
19 {% elif archived %}
20 {% url "archive" page=current_page.previous_page_number %}
20 {% url "archive" page=current_page.previous_page_number %}
21 {% else %}
21 {% else %}
22 {% url "index" page=current_page.previous_page_number %}
22 {% url "index" page=current_page.previous_page_number %}
23 {% endif %}
23 {% endif %}
24 " />
24 " />
25 {% endif %}
25 {% endif %}
26 {% if current_page.has_next %}
26 {% if current_page.has_next %}
27 <link rel="next" href="
27 <link rel="next" href="
28 {% if tag %}
28 {% if tag %}
29 {% url "tag" tag_name=tag page=current_page.next_page_number %}
29 {% url "tag" tag_name=tag page=current_page.next_page_number %}
30 {% elif archived %}
30 {% elif archived %}
31 {% url "archive" page=current_page.next_page_number %}
31 {% url "archive" page=current_page.next_page_number %}
32 {% else %}
32 {% else %}
33 {% url "index" page=current_page.next_page_number %}
33 {% url "index" page=current_page.next_page_number %}
34 {% endif %}
34 {% endif %}
35 " />
35 " />
36 {% endif %}
36 {% endif %}
37
37
38 {% endblock %}
38 {% endblock %}
39
39
40 {% block content %}
40 {% block content %}
41
41
42 {% get_current_language as LANGUAGE_CODE %}
42 {% get_current_language as LANGUAGE_CODE %}
43
43
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>
58 {% else %}
58 {% else %}
59 <a href="{% url 'tag' tag.name %}?method=hide&next={{ request.path }}"
59 <a href="{% url 'tag' tag.name %}?method=hide&next={{ request.path }}"
60 title="{% trans 'Hide tag' %}"
60 title="{% trans 'Hide tag' %}"
61 class="not_fav">H</a>
61 class="not_fav">H</a>
62 {% endif %}
62 {% endif %}
63 #{{ tag.name }}
63 #{{ tag.name }}
64 </h2>
64 </h2>
65 </div>
65 </div>
66 {% endif %}
66 {% endif %}
67
67
68 {% if threads %}
68 {% if threads %}
69 {% if current_page.has_previous %}
69 {% if current_page.has_previous %}
70 <div class="page_link">
70 <div class="page_link">
71 <a href="
71 <a href="
72 {% if tag %}
72 {% if tag %}
73 {% url "tag" tag_name=tag page=current_page.previous_page_number %}
73 {% url "tag" tag_name=tag page=current_page.previous_page_number %}
74 {% elif archived %}
74 {% elif archived %}
75 {% url "archive" page=current_page.previous_page_number %}
75 {% url "archive" page=current_page.previous_page_number %}
76 {% else %}
76 {% else %}
77 {% url "index" page=current_page.previous_page_number %}
77 {% url "index" page=current_page.previous_page_number %}
78 {% endif %}
78 {% endif %}
79 ">{% trans "Previous page" %}</a>
79 ">{% trans "Previous page" %}</a>
80 </div>
80 </div>
81 {% endif %}
81 {% endif %}
82
82
83 {% for thread in threads %}
83 {% for thread in threads %}
84 {% cache 600 thread_short thread.id thread.last_edit_time moderator LANGUAGE_CODE %}
84 {% cache 600 thread_short thread.id thread.last_edit_time moderator LANGUAGE_CODE %}
85 <div class="thread">
85 <div class="thread">
86 {% with can_bump=thread.can_bump %}
86 {% with can_bump=thread.can_bump %}
87 {% post_view thread.get_opening_post moderator is_opening=True thread=thread can_bump=can_bump truncated=True need_open_link=True %}
87 {% post_view thread.get_opening_post moderator is_opening=True thread=thread can_bump=can_bump truncated=True need_open_link=True %}
88 {% if not thread.archived %}
88 {% if not thread.archived %}
89 {% with last_replies=thread.get_last_replies %}
89 {% with last_replies=thread.get_last_replies %}
90 {% if last_replies %}
90 {% if last_replies %}
91 {% if thread.get_skipped_replies_count %}
91 {% if thread.get_skipped_replies_count %}
92 <div class="skipped_replies">
92 <div class="skipped_replies">
93 <a href="{% url 'thread' thread.get_opening_post.id %}">
93 <a href="{% url 'thread' thread.get_opening_post.id %}">
94 {% blocktrans with count=thread.get_skipped_replies_count %}Skipped {{ count }} replies. Open thread to see all replies.{% endblocktrans %}
94 {% blocktrans with count=thread.get_skipped_replies_count %}Skipped {{ count }} replies. Open thread to see all replies.{% endblocktrans %}
95 </a>
95 </a>
96 </div>
96 </div>
97 {% endif %}
97 {% endif %}
98 <div class="last-replies">
98 <div class="last-replies">
99 {% for post in last_replies %}
99 {% for post in last_replies %}
100 {% post_view post moderator=moderator is_opening=False thread=thread can_bump=can_bump truncated=True %}
100 {% post_view post moderator=moderator is_opening=False thread=thread can_bump=can_bump truncated=True %}
101 {% endfor %}
101 {% endfor %}
102 </div>
102 </div>
103 {% endif %}
103 {% endif %}
104 {% endwith %}
104 {% endwith %}
105 {% endif %}
105 {% endif %}
106 {% endwith %}
106 {% endwith %}
107 </div>
107 </div>
108 {% endcache %}
108 {% endcache %}
109 {% endfor %}
109 {% endfor %}
110
110
111 {% if current_page.has_next %}
111 {% if current_page.has_next %}
112 <div class="page_link">
112 <div class="page_link">
113 <a href="
113 <a href="
114 {% if tag %}
114 {% if tag %}
115 {% url "tag" tag_name=tag page=current_page.next_page_number %}
115 {% url "tag" tag_name=tag page=current_page.next_page_number %}
116 {% elif archived %}
116 {% elif archived %}
117 {% url "archive" page=current_page.next_page_number %}
117 {% url "archive" page=current_page.next_page_number %}
118 {% else %}
118 {% else %}
119 {% url "index" page=current_page.next_page_number %}
119 {% url "index" page=current_page.next_page_number %}
120 {% endif %}
120 {% endif %}
121 ">{% trans "Next page" %}</a>
121 ">{% trans "Next page" %}</a>
122 </div>
122 </div>
123 {% endif %}
123 {% endif %}
124 {% else %}
124 {% else %}
125 <div class="post">
125 <div class="post">
126 {% trans 'No threads exist. Create the first one!' %}</div>
126 {% trans 'No threads exist. Create the first one!' %}</div>
127 {% endif %}
127 {% endif %}
128
128
129 <div class="post-form-w">
129 <div class="post-form-w">
130 <script src="{% static 'js/panel.js' %}"></script>
130 <script src="{% static 'js/panel.js' %}"></script>
131 <div class="post-form">
131 <div class="post-form">
132 <div class="form-title">{% trans "Create new thread" %}</div>
132 <div class="form-title">{% trans "Create new thread" %}</div>
133 <div class="swappable-form-full">
133 <div class="swappable-form-full">
134 <form enctype="multipart/form-data" method="post">{% csrf_token %}
134 <form enctype="multipart/form-data" method="post">{% csrf_token %}
135 {{ form.as_div }}
135 {{ form.as_div }}
136 <div class="form-submit">
136 <div class="form-submit">
137 <input type="submit" value="{% trans "Post" %}"/>
137 <input type="submit" value="{% trans "Post" %}"/>
138 </div>
138 </div>
139 </form>
139 </form>
140 </div>
140 </div>
141 <div>
141 <div>
142 {% trans 'Tags must be delimited by spaces. Text or image is required.' %}
142 {% trans 'Tags must be delimited by spaces. Text or image is required.' %}
143 </div>
143 </div>
144 <div><a href="{% url "staticpage" name="help" %}">
144 <div><a href="{% url "staticpage" name="help" %}">
145 {% trans 'Text syntax' %}</a></div>
145 {% trans 'Text syntax' %}</a></div>
146 </div>
146 </div>
147 </div>
147 </div>
148
148
149 <script src="{% static 'js/form.js' %}"></script>
149 <script src="{% static 'js/form.js' %}"></script>
150
150
151 {% endblock %}
151 {% endblock %}
152
152
153 {% block metapanel %}
153 {% block metapanel %}
154
154
155 <span class="metapanel">
155 <span class="metapanel">
156 <b><a href="{% url "authors" %}">{{ site_name }}</a> {{ version }}</b>
156 <b><a href="{% url "authors" %}">{{ site_name }}</a> {{ version }}</b>
157 {% trans "Pages:" %}
157 {% trans "Pages:" %}
158 <a href="
158 <a href="
159 {% if tag %}
159 {% if tag %}
160 {% url "tag" tag_name=tag page=paginator.page_range|first %}
160 {% url "tag" tag_name=tag page=paginator.page_range|first %}
161 {% elif archived %}
161 {% elif archived %}
162 {% url "archive" page=paginator.page_range|first %}
162 {% url "archive" page=paginator.page_range|first %}
163 {% else %}
163 {% else %}
164 {% url "index" page=paginator.page_range|first %}
164 {% url "index" page=paginator.page_range|first %}
165 {% endif %}
165 {% endif %}
166 ">&lt;&lt;</a>
166 ">&lt;&lt;</a>
167 [
167 [
168 {% for page in paginator.center_range %}
168 {% for page in paginator.center_range %}
169 <a
169 <a
170 {% ifequal page current_page.number %}
170 {% ifequal page current_page.number %}
171 class="current_page"
171 class="current_page"
172 {% endifequal %}
172 {% endifequal %}
173 href="
173 href="
174 {% if tag %}
174 {% if tag %}
175 {% url "tag" tag_name=tag page=page %}
175 {% url "tag" tag_name=tag page=page %}
176 {% elif archived %}
176 {% elif archived %}
177 {% url "archive" page=page %}
177 {% url "archive" page=page %}
178 {% else %}
178 {% else %}
179 {% url "index" page=page %}
179 {% url "index" page=page %}
180 {% endif %}
180 {% endif %}
181 ">{{ page }}</a>
181 ">{{ page }}</a>
182 {% if not forloop.last %},{% endif %}
182 {% if not forloop.last %},{% endif %}
183 {% endfor %}
183 {% endfor %}
184 ]
184 ]
185 <a href="
185 <a href="
186 {% if tag %}
186 {% if tag %}
187 {% url "tag" tag_name=tag page=paginator.page_range|last %}
187 {% url "tag" tag_name=tag page=paginator.page_range|last %}
188 {% elif archived %}
188 {% elif archived %}
189 {% url "archive" page=paginator.page_range|last %}
189 {% url "archive" page=paginator.page_range|last %}
190 {% else %}
190 {% else %}
191 {% url "index" page=paginator.page_range|last %}
191 {% url "index" page=paginator.page_range|last %}
192 {% endif %}
192 {% endif %}
193 ">&gt;&gt;</a>
193 ">&gt;&gt;</a>
194 [<a href="rss/">RSS</a>]
194 [<a href="rss/">RSS</a>]
195 </span>
195 </span>
196
196
197 {% endblock %}
197 {% endblock %}
@@ -1,50 +1,41 b''
1 {% extends "boards/base.html" %}
1 {% extends "boards/base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4 {% load humanize %}
4 {% load humanize %}
5
5
6 {% block head %}
6 {% block head %}
7 <title>{% trans 'Settings' %} - {{ site_name }}</title>
7 <title>{% trans 'Settings' %} - {{ site_name }}</title>
8 {% endblock %}
8 {% endblock %}
9
9
10 {% block content %}
10 {% block content %}
11
11
12 <div class="post">
12 <div class="post">
13 <p>
13 <p>
14 {% trans 'User:' %} <b>{{ user.user_id }}</b>.
15 {% if user.is_moderator %}
14 {% 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>
18 {% with hidden_tags=hidden_tags %}
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 %}
19 {% if hidden_tags %}
29 <p>{% trans 'Hidden tags:' %}
20 <p>{% trans 'Hidden tags:' %}
30 {% for tag in hidden_tags %}
21 {% for tag in hidden_tags %}
31 <a class="tag" href="{% url 'tag' tag.name %}">
22 <a class="tag" href="{% url 'tag' tag.name %}">
32 #{{ tag.name }}</a>{% if not forloop.last %},{% endif %}
23 #{{ tag.name }}</a>{% if not forloop.last %},{% endif %}
33 {% endfor %}
24 {% endfor %}
34 </p>
25 </p>
35 {% endif %}
26 {% endif %}
36 {% endwith %}
27 {% endwith %}
37 </div>
28 </div>
38
29
39 <div class="post-form-w">
30 <div class="post-form-w">
40 <div class="post-form">
31 <div class="post-form">
41 <form method="post">{% csrf_token %}
32 <form method="post">{% csrf_token %}
42 {{ form.as_div }}
33 {{ form.as_div }}
43 <div class="form-submit">
34 <div class="form-submit">
44 <input type="submit" value="{% trans "Save" %}" />
35 <input type="submit" value="{% trans "Save" %}" />
45 </div>
36 </div>
46 </form>
37 </form>
47 </div>
38 </div>
48 </div>
39 </div>
49
40
50 {% endblock %}
41 {% endblock %}
@@ -1,260 +1,259 b''
1 # coding=utf-8
1 # coding=utf-8
2 import time
2 import time
3 import logging
3 import logging
4 from django.core.paginator import Paginator
4 from django.core.paginator import Paginator
5
5
6 from django.test import TestCase
6 from django.test import TestCase
7 from django.test.client import Client
7 from django.test.client import Client
8 from django.core.urlresolvers import reverse, NoReverseMatch
8 from django.core.urlresolvers import reverse, NoReverseMatch
9
9
10 from boards.models import Post, Tag, Thread
10 from boards.models import Post, Tag, Thread
11 from boards import urls
11 from boards import urls
12 from boards import settings
12 from boards import settings
13 import neboard
13 import neboard
14
14
15 PAGE_404 = 'boards/404.html'
15 PAGE_404 = 'boards/404.html'
16
16
17 TEST_TEXT = 'test text'
17 TEST_TEXT = 'test text'
18
18
19 NEW_THREAD_PAGE = '/'
19 NEW_THREAD_PAGE = '/'
20 THREAD_PAGE_ONE = '/thread/1/'
20 THREAD_PAGE_ONE = '/thread/1/'
21 THREAD_PAGE = '/thread/'
21 THREAD_PAGE = '/thread/'
22 TAG_PAGE = '/tag/'
22 TAG_PAGE = '/tag/'
23 HTTP_CODE_REDIRECT = 302
23 HTTP_CODE_REDIRECT = 302
24 HTTP_CODE_OK = 200
24 HTTP_CODE_OK = 200
25 HTTP_CODE_NOT_FOUND = 404
25 HTTP_CODE_NOT_FOUND = 404
26
26
27 logger = logging.getLogger(__name__)
27 logger = logging.getLogger(__name__)
28
28
29
29
30 class PostTests(TestCase):
30 class PostTests(TestCase):
31
31
32 def _create_post(self):
32 def _create_post(self):
33 return Post.objects.create_post(title='title',
33 return Post.objects.create_post(title='title', text='text')
34 text='text')
35
34
36 def test_post_add(self):
35 def test_post_add(self):
37 """Test adding post"""
36 """Test adding post"""
38
37
39 post = self._create_post()
38 post = self._create_post()
40
39
41 self.assertIsNotNone(post, 'No post was created')
40 self.assertIsNotNone(post, 'No post was created')
42
41
43 def test_delete_post(self):
42 def test_delete_post(self):
44 """Test post deletion"""
43 """Test post deletion"""
45
44
46 post = self._create_post()
45 post = self._create_post()
47 post_id = post.id
46 post_id = post.id
48
47
49 Post.objects.delete_post(post)
48 Post.objects.delete_post(post)
50
49
51 self.assertFalse(Post.objects.filter(id=post_id).exists())
50 self.assertFalse(Post.objects.filter(id=post_id).exists())
52
51
53 def test_delete_thread(self):
52 def test_delete_thread(self):
54 """Test thread deletion"""
53 """Test thread deletion"""
55
54
56 opening_post = self._create_post()
55 opening_post = self._create_post()
57 thread = opening_post.get_thread()
56 thread = opening_post.get_thread()
58 reply = Post.objects.create_post("", "", thread=thread)
57 reply = Post.objects.create_post("", "", thread=thread)
59
58
60 thread.delete()
59 thread.delete()
61
60
62 self.assertFalse(Post.objects.filter(id=reply.id).exists())
61 self.assertFalse(Post.objects.filter(id=reply.id).exists())
63
62
64 def test_post_to_thread(self):
63 def test_post_to_thread(self):
65 """Test adding post to a thread"""
64 """Test adding post to a thread"""
66
65
67 op = self._create_post()
66 op = self._create_post()
68 post = Post.objects.create_post("", "", thread=op.get_thread())
67 post = Post.objects.create_post("", "", thread=op.get_thread())
69
68
70 self.assertIsNotNone(post, 'Reply to thread wasn\'t created')
69 self.assertIsNotNone(post, 'Reply to thread wasn\'t created')
71 self.assertEqual(op.get_thread().last_edit_time, post.pub_time,
70 self.assertEqual(op.get_thread().last_edit_time, post.pub_time,
72 'Post\'s create time doesn\'t match thread last edit'
71 'Post\'s create time doesn\'t match thread last edit'
73 ' time')
72 ' time')
74
73
75 def test_delete_posts_by_ip(self):
74 def test_delete_posts_by_ip(self):
76 """Test deleting posts with the given ip"""
75 """Test deleting posts with the given ip"""
77
76
78 post = self._create_post()
77 post = self._create_post()
79 post_id = post.id
78 post_id = post.id
80
79
81 Post.objects.delete_posts_by_ip('0.0.0.0')
80 Post.objects.delete_posts_by_ip('0.0.0.0')
82
81
83 self.assertFalse(Post.objects.filter(id=post_id).exists())
82 self.assertFalse(Post.objects.filter(id=post_id).exists())
84
83
85 def test_get_thread(self):
84 def test_get_thread(self):
86 """Test getting all posts of a thread"""
85 """Test getting all posts of a thread"""
87
86
88 opening_post = self._create_post()
87 opening_post = self._create_post()
89
88
90 for i in range(0, 2):
89 for i in range(0, 2):
91 Post.objects.create_post('title', 'text',
90 Post.objects.create_post('title', 'text',
92 thread=opening_post.get_thread())
91 thread=opening_post.get_thread())
93
92
94 thread = opening_post.get_thread()
93 thread = opening_post.get_thread()
95
94
96 self.assertEqual(3, thread.replies.count())
95 self.assertEqual(3, thread.replies.count())
97
96
98 def test_create_post_with_tag(self):
97 def test_create_post_with_tag(self):
99 """Test adding tag to post"""
98 """Test adding tag to post"""
100
99
101 tag = Tag.objects.create(name='test_tag')
100 tag = Tag.objects.create(name='test_tag')
102 post = Post.objects.create_post(title='title', text='text', tags=[tag])
101 post = Post.objects.create_post(title='title', text='text', tags=[tag])
103
102
104 thread = post.get_thread()
103 thread = post.get_thread()
105 self.assertIsNotNone(post, 'Post not created')
104 self.assertIsNotNone(post, 'Post not created')
106 self.assertTrue(tag in thread.tags.all(), 'Tag not added to thread')
105 self.assertTrue(tag in thread.tags.all(), 'Tag not added to thread')
107 self.assertTrue(thread in tag.threads.all(), 'Thread not added to tag')
106 self.assertTrue(thread in tag.threads.all(), 'Thread not added to tag')
108
107
109 def test_thread_max_count(self):
108 def test_thread_max_count(self):
110 """Test deletion of old posts when the max thread count is reached"""
109 """Test deletion of old posts when the max thread count is reached"""
111
110
112 for i in range(settings.MAX_THREAD_COUNT + 1):
111 for i in range(settings.MAX_THREAD_COUNT + 1):
113 self._create_post()
112 self._create_post()
114
113
115 self.assertEqual(settings.MAX_THREAD_COUNT,
114 self.assertEqual(settings.MAX_THREAD_COUNT,
116 len(Thread.objects.filter(archived=False)))
115 len(Thread.objects.filter(archived=False)))
117
116
118 def test_pages(self):
117 def test_pages(self):
119 """Test that the thread list is properly split into pages"""
118 """Test that the thread list is properly split into pages"""
120
119
121 for i in range(settings.MAX_THREAD_COUNT):
120 for i in range(settings.MAX_THREAD_COUNT):
122 self._create_post()
121 self._create_post()
123
122
124 all_threads = Thread.objects.filter(archived=False)
123 all_threads = Thread.objects.filter(archived=False)
125
124
126 paginator = Paginator(Thread.objects.filter(archived=False),
125 paginator = Paginator(Thread.objects.filter(archived=False),
127 settings.THREADS_PER_PAGE)
126 settings.THREADS_PER_PAGE)
128 posts_in_second_page = paginator.page(2).object_list
127 posts_in_second_page = paginator.page(2).object_list
129 first_post = posts_in_second_page[0]
128 first_post = posts_in_second_page[0]
130
129
131 self.assertEqual(all_threads[settings.THREADS_PER_PAGE].id,
130 self.assertEqual(all_threads[settings.THREADS_PER_PAGE].id,
132 first_post.id)
131 first_post.id)
133
132
134 def test_linked_tag(self):
133 def test_linked_tag(self):
135 """Test adding a linked tag"""
134 """Test adding a linked tag"""
136
135
137 linked_tag = Tag.objects.create(name=u'tag1')
136 linked_tag = Tag.objects.create(name=u'tag1')
138 tag = Tag.objects.create(name=u'tag2', linked=linked_tag)
137 tag = Tag.objects.create(name=u'tag2', linked=linked_tag)
139
138
140 post = Post.objects.create_post("", "", tags=[tag])
139 post = Post.objects.create_post("", "", tags=[tag])
141
140
142 self.assertTrue(linked_tag in post.get_thread().tags.all(),
141 self.assertTrue(linked_tag in post.get_thread().tags.all(),
143 'Linked tag was not added')
142 'Linked tag was not added')
144
143
145
144
146 class PagesTest(TestCase):
145 class PagesTest(TestCase):
147
146
148 def test_404(self):
147 def test_404(self):
149 """Test receiving error 404 when opening a non-existent page"""
148 """Test receiving error 404 when opening a non-existent page"""
150
149
151 tag_name = u'test_tag'
150 tag_name = u'test_tag'
152 tag = Tag.objects.create(name=tag_name)
151 tag = Tag.objects.create(name=tag_name)
153 client = Client()
152 client = Client()
154
153
155 Post.objects.create_post('title', TEST_TEXT, tags=[tag])
154 Post.objects.create_post('title', TEST_TEXT, tags=[tag])
156
155
157 existing_post_id = Post.objects.all()[0].id
156 existing_post_id = Post.objects.all()[0].id
158 response_existing = client.get(THREAD_PAGE + str(existing_post_id) +
157 response_existing = client.get(THREAD_PAGE + str(existing_post_id) +
159 '/')
158 '/')
160 self.assertEqual(HTTP_CODE_OK, response_existing.status_code,
159 self.assertEqual(HTTP_CODE_OK, response_existing.status_code,
161 u'Cannot open existing thread')
160 u'Cannot open existing thread')
162
161
163 response_not_existing = client.get(THREAD_PAGE + str(
162 response_not_existing = client.get(THREAD_PAGE + str(
164 existing_post_id + 1) + '/')
163 existing_post_id + 1) + '/')
165 self.assertEqual(PAGE_404, response_not_existing.templates[0].name,
164 self.assertEqual(PAGE_404, 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 + '/')
169 self.assertEqual(HTTP_CODE_OK,
168 self.assertEqual(HTTP_CODE_OK,
170 response_existing.status_code,
169 response_existing.status_code,
171 u'Cannot open existing tag')
170 u'Cannot open existing tag')
172
171
173 response_not_existing = client.get(TAG_PAGE + u'not_tag' + '/')
172 response_not_existing = client.get(TAG_PAGE + u'not_tag' + '/')
174 self.assertEqual(PAGE_404,
173 self.assertEqual(PAGE_404,
175 response_not_existing.templates[0].name,
174 response_not_existing.templates[0].name,
176 u'Not existing tag is opened')
175 u'Not existing tag is opened')
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 .get_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,
184 response_not_existing.templates[0].name,
183 response_not_existing.templates[0].name,
185 u'Reply is opened as a thread')
184 u'Reply is opened as a thread')
186
185
187
186
188 class FormTest(TestCase):
187 class FormTest(TestCase):
189 def test_post_validation(self):
188 def test_post_validation(self):
190 # Disable captcha for the test
189 # Disable captcha for the test
191 captcha_enabled = neboard.settings.ENABLE_CAPTCHA
190 captcha_enabled = neboard.settings.ENABLE_CAPTCHA
192 neboard.settings.ENABLE_CAPTCHA = False
191 neboard.settings.ENABLE_CAPTCHA = False
193
192
194 client = Client()
193 client = Client()
195
194
196 valid_tags = u'tag1 tag_2 тег_3'
195 valid_tags = u'tag1 tag_2 тег_3'
197 invalid_tags = u'$%_356 ---'
196 invalid_tags = u'$%_356 ---'
198
197
199 response = client.post(NEW_THREAD_PAGE, {'title': 'test title',
198 response = client.post(NEW_THREAD_PAGE, {'title': 'test title',
200 'text': TEST_TEXT,
199 'text': TEST_TEXT,
201 'tags': valid_tags})
200 'tags': valid_tags})
202 self.assertEqual(response.status_code, HTTP_CODE_REDIRECT,
201 self.assertEqual(response.status_code, HTTP_CODE_REDIRECT,
203 msg='Posting new message failed: got code ' +
202 msg='Posting new message failed: got code ' +
204 str(response.status_code))
203 str(response.status_code))
205
204
206 self.assertEqual(1, Post.objects.count(),
205 self.assertEqual(1, Post.objects.count(),
207 msg='No posts were created')
206 msg='No posts were created')
208
207
209 client.post(NEW_THREAD_PAGE, {'text': TEST_TEXT,
208 client.post(NEW_THREAD_PAGE, {'text': TEST_TEXT,
210 'tags': invalid_tags})
209 'tags': invalid_tags})
211 self.assertEqual(1, Post.objects.count(), msg='The validation passed '
210 self.assertEqual(1, Post.objects.count(), msg='The validation passed '
212 'where it should fail')
211 'where it should fail')
213
212
214 # Change posting delay so we don't have to wait for 30 seconds or more
213 # Change posting delay so we don't have to wait for 30 seconds or more
215 old_posting_delay = neboard.settings.POSTING_DELAY
214 old_posting_delay = neboard.settings.POSTING_DELAY
216 # Wait fot the posting delay or we won't be able to post
215 # Wait fot the posting delay or we won't be able to post
217 settings.POSTING_DELAY = 1
216 settings.POSTING_DELAY = 1
218 time.sleep(neboard.settings.POSTING_DELAY + 1)
217 time.sleep(neboard.settings.POSTING_DELAY + 1)
219 response = client.post(THREAD_PAGE_ONE, {'text': TEST_TEXT,
218 response = client.post(THREAD_PAGE_ONE, {'text': TEST_TEXT,
220 'tags': valid_tags})
219 'tags': valid_tags})
221 self.assertEqual(HTTP_CODE_REDIRECT, response.status_code,
220 self.assertEqual(HTTP_CODE_REDIRECT, response.status_code,
222 msg=u'Posting new message failed: got code ' +
221 msg=u'Posting new message failed: got code ' +
223 str(response.status_code))
222 str(response.status_code))
224 # Restore posting delay
223 # Restore posting delay
225 settings.POSTING_DELAY = old_posting_delay
224 settings.POSTING_DELAY = old_posting_delay
226
225
227 self.assertEqual(2, Post.objects.count(),
226 self.assertEqual(2, Post.objects.count(),
228 msg=u'No posts were created')
227 msg=u'No posts were created')
229
228
230 # Restore captcha setting
229 # Restore captcha setting
231 settings.ENABLE_CAPTCHA = captcha_enabled
230 settings.ENABLE_CAPTCHA = captcha_enabled
232
231
233
232
234 class ViewTest(TestCase):
233 class ViewTest(TestCase):
235
234
236 def test_all_views(self):
235 def test_all_views(self):
237 '''
236 '''
238 Try opening all views defined in ulrs.py that don't need additional
237 Try opening all views defined in ulrs.py that don't need additional
239 parameters
238 parameters
240 '''
239 '''
241
240
242 client = Client()
241 client = Client()
243 for url in urls.urlpatterns:
242 for url in urls.urlpatterns:
244 try:
243 try:
245 view_name = url.name
244 view_name = url.name
246 logger.debug('Testing view %s' % view_name)
245 logger.debug('Testing view %s' % view_name)
247
246
248 try:
247 try:
249 response = client.get(reverse(view_name))
248 response = client.get(reverse(view_name))
250
249
251 self.assertEqual(HTTP_CODE_OK, response.status_code,
250 self.assertEqual(HTTP_CODE_OK, response.status_code,
252 '%s view not opened' % view_name)
251 '%s view not opened' % view_name)
253 except NoReverseMatch:
252 except NoReverseMatch:
254 # This view just needs additional arguments
253 # This view just needs additional arguments
255 pass
254 pass
256 except Exception, e:
255 except Exception, e:
257 self.fail('Got exception %s at %s view' % (e, view_name))
256 self.fail('Got exception %s at %s view' % (e, view_name))
258 except AttributeError:
257 except AttributeError:
259 # This is normal, some views do not have names
258 # This is normal, some views do not have names
260 pass
259 pass
@@ -1,83 +1,80 b''
1 from django.conf.urls import patterns, url, include
1 from django.conf.urls import patterns, url, include
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 settings, all_tags
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
9 from boards.views.search import BoardSearchView
9 from boards.views.search import BoardSearchView
10 from boards.views.static import StaticPageView
10 from boards.views.static import StaticPageView
11 from boards.views.post_admin import PostAdminView
11 from boards.views.post_admin import PostAdminView
12
12
13 js_info_dict = {
13 js_info_dict = {
14 'packages': ('boards',),
14 'packages': ('boards',),
15 }
15 }
16
16
17 urlpatterns = patterns('',
17 urlpatterns = patterns('',
18
18
19 # /boards/
19 # /boards/
20 url(r'^$', all_threads.AllThreadsView.as_view(), name='index'),
20 url(r'^$', all_threads.AllThreadsView.as_view(), name='index'),
21 # /boards/page/
21 # /boards/page/
22 url(r'^page/(?P<page>\w+)/$', all_threads.AllThreadsView.as_view(),
22 url(r'^page/(?P<page>\w+)/$', all_threads.AllThreadsView.as_view(),
23 name='index'),
23 name='index'),
24
24
25 # login page
26 url(r'^login/$', login.LoginView.as_view(), name='login'),
27
28 # /boards/tag/tag_name/
25 # /boards/tag/tag_name/
29 url(r'^tag/(?P<tag_name>\w+)/$', tag_threads.TagView.as_view(),
26 url(r'^tag/(?P<tag_name>\w+)/$', tag_threads.TagView.as_view(),
30 name='tag'),
27 name='tag'),
31 # /boards/tag/tag_id/page/
28 # /boards/tag/tag_id/page/
32 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/$',
29 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/$',
33 tag_threads.TagView.as_view(), name='tag'),
30 tag_threads.TagView.as_view(), name='tag'),
34
31
35 # /boards/thread/
32 # /boards/thread/
36 url(r'^thread/(?P<post_id>\w+)/$', views.thread.ThreadView.as_view(),
33 url(r'^thread/(?P<post_id>\w+)/$', views.thread.ThreadView.as_view(),
37 name='thread'),
34 name='thread'),
38 url(r'^thread/(?P<post_id>\w+)/mode/(?P<mode>\w+)/$', views.thread.ThreadView
35 url(r'^thread/(?P<post_id>\w+)/mode/(?P<mode>\w+)/$', views.thread.ThreadView
39 .as_view(), name='thread_mode'),
36 .as_view(), name='thread_mode'),
40
37
41 # /boards/post_admin/
38 # /boards/post_admin/
42 url(r'^post_admin/(?P<post_id>\w+)/$', PostAdminView.as_view(),
39 url(r'^post_admin/(?P<post_id>\w+)/$', PostAdminView.as_view(),
43 name='post_admin'),
40 name='post_admin'),
44
41
45 url(r'^settings/$', settings.SettingsView.as_view(), name='settings'),
42 url(r'^settings/$', settings.SettingsView.as_view(), name='settings'),
46 url(r'^tags/$', all_tags.AllTagsView.as_view(), name='tags'),
43 url(r'^tags/$', all_tags.AllTagsView.as_view(), name='tags'),
47 url(r'^captcha/', include('captcha.urls')),
44 url(r'^captcha/', include('captcha.urls')),
48 url(r'^authors/$', AuthorsView.as_view(), name='authors'),
45 url(r'^authors/$', AuthorsView.as_view(), name='authors'),
49 url(r'^delete/(?P<post_id>\w+)/$', DeletePostView.as_view(),
46 url(r'^delete/(?P<post_id>\w+)/$', DeletePostView.as_view(),
50 name='delete'),
47 name='delete'),
51 url(r'^ban/(?P<post_id>\w+)/$', BanUserView.as_view(), name='ban'),
48 url(r'^ban/(?P<post_id>\w+)/$', BanUserView.as_view(), name='ban'),
52
49
53 url(r'^banned/$', views.banned.BannedView.as_view(), name='banned'),
50 url(r'^banned/$', views.banned.BannedView.as_view(), name='banned'),
54 url(r'^staticpage/(?P<name>\w+)/$', StaticPageView.as_view(),
51 url(r'^staticpage/(?P<name>\w+)/$', StaticPageView.as_view(),
55 name='staticpage'),
52 name='staticpage'),
56
53
57 # RSS feeds
54 # RSS feeds
58 url(r'^rss/$', AllThreadsFeed()),
55 url(r'^rss/$', AllThreadsFeed()),
59 url(r'^page/(?P<page>\w+)/rss/$', AllThreadsFeed()),
56 url(r'^page/(?P<page>\w+)/rss/$', AllThreadsFeed()),
60 url(r'^tag/(?P<tag_name>\w+)/rss/$', TagThreadsFeed()),
57 url(r'^tag/(?P<tag_name>\w+)/rss/$', TagThreadsFeed()),
61 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/rss/$', TagThreadsFeed()),
58 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/rss/$', TagThreadsFeed()),
62 url(r'^thread/(?P<post_id>\w+)/rss/$', ThreadPostsFeed()),
59 url(r'^thread/(?P<post_id>\w+)/rss/$', ThreadPostsFeed()),
63
60
64 # i18n
61 # i18n
65 url(r'^jsi18n/$', 'boards.views.cached_js_catalog', js_info_dict,
62 url(r'^jsi18n/$', 'boards.views.cached_js_catalog', js_info_dict,
66 name='js_info_dict'),
63 name='js_info_dict'),
67
64
68 # API
65 # API
69 url(r'^api/post/(?P<post_id>\w+)/$', api.get_post, name="get_post"),
66 url(r'^api/post/(?P<post_id>\w+)/$', api.get_post, name="get_post"),
70 url(r'^api/diff_thread/(?P<thread_id>\w+)/(?P<last_update_time>\w+)/$',
67 url(r'^api/diff_thread/(?P<thread_id>\w+)/(?P<last_update_time>\w+)/$',
71 api.api_get_threaddiff, name="get_thread_diff"),
68 api.api_get_threaddiff, name="get_thread_diff"),
72 url(r'^api/threads/(?P<count>\w+)/$', api.api_get_threads,
69 url(r'^api/threads/(?P<count>\w+)/$', api.api_get_threads,
73 name='get_threads'),
70 name='get_threads'),
74 url(r'^api/tags/$', api.api_get_tags, name='get_tags'),
71 url(r'^api/tags/$', api.api_get_tags, name='get_tags'),
75 url(r'^api/thread/(?P<opening_post_id>\w+)/$', api.api_get_thread_posts,
72 url(r'^api/thread/(?P<opening_post_id>\w+)/$', api.api_get_thread_posts,
76 name='get_thread'),
73 name='get_thread'),
77 url(r'^api/add_post/(?P<opening_post_id>\w+)/$', api.api_add_post,
74 url(r'^api/add_post/(?P<opening_post_id>\w+)/$', api.api_add_post,
78 name='add_post'),
75 name='add_post'),
79
76
80 # Search
77 # Search
81 url(r'^search/$', BoardSearchView.as_view(), name='search'),
78 url(r'^search/$', BoardSearchView.as_view(), name='search'),
82
79
83 )
80 )
@@ -1,129 +1,82 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 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'
16 KEY_CAPTCHA_DELAY_TIME = 'key_captcha_delay_time'
13 KEY_CAPTCHA_DELAY_TIME = 'key_captcha_delay_time'
17 KEY_CAPTCHA_LAST_ACTIVITY = 'key_captcha_last_activity'
14 KEY_CAPTCHA_LAST_ACTIVITY = 'key_captcha_last_activity'
18
15
19
16
20 def need_include_captcha(request):
17 def need_include_captcha(request):
21 """
18 """
22 Check if request is made by a user.
19 Check if request is made by a user.
23 It contains rules which check for bots.
20 It contains rules which check for bots.
24 """
21 """
25
22
26 if not settings.ENABLE_CAPTCHA:
23 if not settings.ENABLE_CAPTCHA:
27 return False
24 return False
28
25
29 enable_captcha = False
26 enable_captcha = False
30
27
31 #newcomer
28 #newcomer
32 if KEY_CAPTCHA_LAST_ACTIVITY not in request.session:
29 if KEY_CAPTCHA_LAST_ACTIVITY not in request.session:
33 return settings.ENABLE_CAPTCHA
30 return settings.ENABLE_CAPTCHA
34
31
35 last_activity = request.session[KEY_CAPTCHA_LAST_ACTIVITY]
32 last_activity = request.session[KEY_CAPTCHA_LAST_ACTIVITY]
36 current_delay = int(time.time()) - last_activity
33 current_delay = int(time.time()) - last_activity
37
34
38 delay_time = (request.session[KEY_CAPTCHA_DELAY_TIME]
35 delay_time = (request.session[KEY_CAPTCHA_DELAY_TIME]
39 if KEY_CAPTCHA_DELAY_TIME in request.session
36 if KEY_CAPTCHA_DELAY_TIME in request.session
40 else settings.CAPTCHA_DEFAULT_SAFE_TIME)
37 else settings.CAPTCHA_DEFAULT_SAFE_TIME)
41
38
42 if current_delay < delay_time:
39 if current_delay < delay_time:
43 enable_captcha = True
40 enable_captcha = True
44
41
45 print 'ENABLING' + str(enable_captcha)
42 print 'ENABLING' + str(enable_captcha)
46
43
47 return enable_captcha
44 return enable_captcha
48
45
49
46
50 def update_captcha_access(request, passed):
47 def update_captcha_access(request, passed):
51 """
48 """
52 Update captcha fields.
49 Update captcha fields.
53 It will reduce delay time if user passed captcha verification and
50 It will reduce delay time if user passed captcha verification and
54 it will increase it otherwise.
51 it will increase it otherwise.
55 """
52 """
56 session = request.session
53 session = request.session
57
54
58 delay_time = (request.session[KEY_CAPTCHA_DELAY_TIME]
55 delay_time = (request.session[KEY_CAPTCHA_DELAY_TIME]
59 if KEY_CAPTCHA_DELAY_TIME in request.session
56 if KEY_CAPTCHA_DELAY_TIME in request.session
60 else settings.CAPTCHA_DEFAULT_SAFE_TIME)
57 else settings.CAPTCHA_DEFAULT_SAFE_TIME)
61
58
62 print "DELAY TIME = " + str(delay_time)
59 print "DELAY TIME = " + str(delay_time)
63
60
64 if passed:
61 if passed:
65 delay_time -= 2 if delay_time >= 7 else 5
62 delay_time -= 2 if delay_time >= 7 else 5
66 else:
63 else:
67 delay_time += 10
64 delay_time += 10
68
65
69 session[KEY_CAPTCHA_LAST_ACTIVITY] = int(time.time())
66 session[KEY_CAPTCHA_LAST_ACTIVITY] = int(time.time())
70 session[KEY_CAPTCHA_DELAY_TIME] = delay_time
67 session[KEY_CAPTCHA_DELAY_TIME] = delay_time
71
68
72
69
73 def get_client_ip(request):
70 def get_client_ip(request):
74 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
71 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
75 if x_forwarded_for:
72 if x_forwarded_for:
76 ip = x_forwarded_for.split(',')[-1].strip()
73 ip = x_forwarded_for.split(',')[-1].strip()
77 else:
74 else:
78 ip = request.META.get('REMOTE_ADDR')
75 ip = request.META.get('REMOTE_ADDR')
79 return ip
76 return ip
80
77
81
78
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
@@ -1,138 +1,139 b''
1 import string
1 import string
2
2
3 from django.db import transaction
3 from django.db import transaction
4 from django.shortcuts import render, redirect
4 from django.shortcuts import render, redirect
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 SettingsManager
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
11 from boards.views.base import BaseBoardView, CONTEXT_FORM
12 from boards.views.base import BaseBoardView, CONTEXT_FORM
12 from boards.views.posting_mixin import PostMixin
13 from boards.views.posting_mixin import PostMixin
13
14
14 FORM_TAGS = 'tags'
15 FORM_TAGS = 'tags'
15 FORM_TEXT = 'text'
16 FORM_TEXT = 'text'
16 FORM_TITLE = 'title'
17 FORM_TITLE = 'title'
17 FORM_IMAGE = 'image'
18 FORM_IMAGE = 'image'
18
19
19 TAG_DELIMITER = ' '
20 TAG_DELIMITER = ' '
20
21
21 PARAMETER_CURRENT_PAGE = 'current_page'
22 PARAMETER_CURRENT_PAGE = 'current_page'
22 PARAMETER_PAGINATOR = 'paginator'
23 PARAMETER_PAGINATOR = 'paginator'
23 PARAMETER_THREADS = 'threads'
24 PARAMETER_THREADS = 'threads'
24
25
25 TEMPLATE = 'boards/posting_general.html'
26 TEMPLATE = 'boards/posting_general.html'
26 DEFAULT_PAGE = 1
27 DEFAULT_PAGE = 1
27
28
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 = SettingsManager(request.session)
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)
44
46
45 threads = paginator.page(page).object_list
47 threads = paginator.page(page).object_list
46
48
47 context[PARAMETER_THREADS] = threads
49 context[PARAMETER_THREADS] = threads
48 context[CONTEXT_FORM] = form
50 context[CONTEXT_FORM] = form
49
51
50 self._get_page_context(paginator, context, page)
52 self._get_page_context(paginator, context, page)
51
53
52 return render(request, TEMPLATE, context)
54 return render(request, TEMPLATE, context)
53
55
54 def post(self, request, page=DEFAULT_PAGE):
56 def post(self, request, page=DEFAULT_PAGE):
55 form = ThreadForm(request.POST, request.FILES,
57 form = ThreadForm(request.POST, request.FILES,
56 error_class=PlainErrorList)
58 error_class=PlainErrorList)
57 form.session = request.session
59 form.session = request.session
58
60
59 if form.is_valid():
61 if form.is_valid():
60 return self.create_thread(request, form)
62 return self.create_thread(request, form)
61 if form.need_to_ban:
63 if form.need_to_ban:
62 # Ban user because he is suspected to be a bot
64 # Ban user because he is suspected to be a bot
63 self._ban_current_user(request)
65 self._ban_current_user(request)
64
66
65 return self.get(request, page, form)
67 return self.get(request, page, form)
66
68
67 @staticmethod
69 @staticmethod
68 def _get_page_context(paginator, context, page):
70 def _get_page_context(paginator, context, page):
69 """
71 """
70 Get pagination context variables
72 Get pagination context variables
71 """
73 """
72
74
73 context[PARAMETER_PAGINATOR] = paginator
75 context[PARAMETER_PAGINATOR] = paginator
74 context[PARAMETER_CURRENT_PAGE] = paginator.page(int(page))
76 context[PARAMETER_CURRENT_PAGE] = paginator.page(int(page))
75
77
76 @staticmethod
78 @staticmethod
77 def parse_tags_string(tag_strings):
79 def parse_tags_string(tag_strings):
78 """
80 """
79 Parses tag list string and returns tag object list.
81 Parses tag list string and returns tag object list.
80 """
82 """
81
83
82 tags = []
84 tags = []
83
85
84 if tag_strings:
86 if tag_strings:
85 tag_strings = tag_strings.split(TAG_DELIMITER)
87 tag_strings = tag_strings.split(TAG_DELIMITER)
86 for tag_name in tag_strings:
88 for tag_name in tag_strings:
87 tag_name = string.lower(tag_name.strip())
89 tag_name = string.lower(tag_name.strip())
88 if len(tag_name) > 0:
90 if len(tag_name) > 0:
89 tag, created = Tag.objects.get_or_create(name=tag_name)
91 tag, created = Tag.objects.get_or_create(name=tag_name)
90 tags.append(tag)
92 tags.append(tag)
91
93
92 return tags
94 return tags
93
95
94 @transaction.atomic
96 @transaction.atomic
95 def create_thread(self, request, form, html_response=True):
97 def create_thread(self, request, form, html_response=True):
96 """
98 """
97 Creates a new thread with an opening post.
99 Creates a new thread with an opening post.
98 """
100 """
99
101
100 ip = utils.get_client_ip(request)
102 ip = utils.get_client_ip(request)
101 is_banned = Ban.objects.filter(ip=ip).exists()
103 is_banned = Ban.objects.filter(ip=ip).exists()
102
104
103 if is_banned:
105 if is_banned:
104 if html_response:
106 if html_response:
105 return redirect(BannedView().as_view())
107 return redirect(BannedView().as_view())
106 else:
108 else:
107 return
109 return
108
110
109 data = form.cleaned_data
111 data = form.cleaned_data
110
112
111 title = data[FORM_TITLE]
113 title = data[FORM_TITLE]
112 text = data[FORM_TEXT]
114 text = data[FORM_TEXT]
113
115
114 text = self._remove_invalid_links(text)
116 text = self._remove_invalid_links(text)
115
117
116 if FORM_IMAGE in data.keys():
118 if FORM_IMAGE in data.keys():
117 image = data[FORM_IMAGE]
119 image = data[FORM_IMAGE]
118 else:
120 else:
119 image = None
121 image = None
120
122
121 tag_strings = data[FORM_TAGS]
123 tag_strings = data[FORM_TAGS]
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())
131
132
132 def get_threads(self):
133 def get_threads(self):
133 """
134 """
134 Gets list of threads that will be shown on a page.
135 Gets list of threads that will be shown on a page.
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,24 +1,26 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 SettingsManager, \
5 PERMISSION_MODERATE
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
8
9
9
10
10 class BanUserView(BaseBoardView, RedirectNextMixin):
11 class BanUserView(BaseBoardView, RedirectNextMixin):
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 = SettingsManager(request.session)
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:
21 ban.reason = 'Banned for post ' + str(post_id)
23 ban.reason = 'Banned for post ' + str(post_id)
22 ban.save()
24 ban.save()
23
25
24 return self.redirect_to_next(request)
26 return self.redirect_to_next(request)
@@ -1,27 +1,28 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 SettingsManager, \
5 PERMISSION_MODERATE
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
8
9
9
10
10 class DeletePostView(BaseBoardView, RedirectNextMixin):
11 class DeletePostView(BaseBoardView, RedirectNextMixin):
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 = SettingsManager(request.session)
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
23 if not opening_post:
24 if not opening_post:
24 thread = post.thread_new
25 thread = post.thread_new
25 return redirect('thread', post_id=thread.get_opening_post().id)
26 return redirect('thread', post_id=thread.get_opening_post().id)
26 else:
27 else:
27 return self.redirect_to_next(request)
28 return self.redirect_to_next(request)
@@ -1,57 +1,60 b''
1 from django.shortcuts import render, get_object_or_404, redirect
1 from django.shortcuts import render, get_object_or_404, redirect
2 from boards.abstracts.settingsmanager import SettingsManager, \
3 PERMISSION_MODERATE
2
4
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 = SettingsManager(request.session)
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)
17
20
18 if not form:
21 if not form:
19 dispatch_result = self.dispatch_method(request, post)
22 dispatch_result = self.dispatch_method(request, post)
20 if dispatch_result:
23 if dispatch_result:
21 return dispatch_result
24 return dispatch_result
22 form = AddTagForm()
25 form = AddTagForm()
23
26
24 context = self.get_context_data(request=request)
27 context = self.get_context_data(request=request)
25
28
26 context['post'] = post
29 context['post'] = post
27
30
28 context['tag_form'] = form
31 context['tag_form'] = form
29
32
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 = SettingsManager(request.session)
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)
38 return self.dispatch_method(request, post)
41 return self.dispatch_method(request, post)
39
42
40 def delete_tag(self, request, post):
43 def delete_tag(self, request, post):
41 tag_name = request.GET['tag']
44 tag_name = request.GET['tag']
42 tag = get_object_or_404(Tag, name=tag_name)
45 tag = get_object_or_404(Tag, name=tag_name)
43
46
44 post.remove_tag(tag)
47 post.remove_tag(tag)
45
48
46 return redirect('post_admin', post.id)
49 return redirect('post_admin', post.id)
47
50
48 def add_tag(self, request, post):
51 def add_tag(self, request, post):
49 form = AddTagForm(request.POST, error_class=PlainErrorList)
52 form = AddTagForm(request.POST, error_class=PlainErrorList)
50 if form.is_valid():
53 if form.is_valid():
51 tag_name = form.cleaned_data['tag']
54 tag_name = form.cleaned_data['tag']
52 tag, created = Tag.objects.get_or_create(name=tag_name)
55 tag, created = Tag.objects.get_or_create(name=tag_name)
53
56
54 post.add_tag(tag)
57 post.add_tag(tag)
55 return redirect('post_admin', post.id)
58 return redirect('post_admin', post.id)
56 else:
59 else:
57 return self.get(request, post.id, form)
60 return self.get(request, post.id, form)
@@ -1,53 +1,55 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 from boards import utils
4 from boards.abstracts.settingsmanager import SettingsManager, \
5 PERMISSION_MODERATE
4
6
5 from boards.views.base import BaseBoardView, CONTEXT_FORM
7 from boards.views.base import BaseBoardView, CONTEXT_FORM
6 from boards.forms import SettingsForm, ModeratorSettingsForm, PlainErrorList
8 from boards.forms import SettingsForm, ModeratorSettingsForm, PlainErrorList
7 from boards.models.post import SETTING_MODERATE
9 from boards.models.post import SETTING_MODERATE
8
10
9
11
10 class SettingsView(BaseBoardView):
12 class SettingsView(BaseBoardView):
11
13
12 def get(self, request):
14 def get(self, request):
13 context = self.get_context_data(request=request)
15 context = self.get_context_data(request=request)
14 user = utils.get_user(request)
16 settings_manager = SettingsManager(request.session)
15 is_moderator = user.is_moderator()
17 is_moderator = settings_manager.has_permission(PERMISSION_MODERATE)
16
18
17 selected_theme = utils.get_theme(request, user)
19 selected_theme = settings_manager.get_theme()
18
20
19 if is_moderator:
21 if is_moderator:
20 form = ModeratorSettingsForm(initial={
22 form = ModeratorSettingsForm(initial={
21 'theme': selected_theme,
23 'theme': selected_theme,
22 'moderate': user.get_setting(SETTING_MODERATE) and \
24 'moderate': settings_manager.has_permission(PERMISSION_MODERATE)
23 user.is_moderator()
24 }, error_class=PlainErrorList)
25 }, error_class=PlainErrorList)
25 else:
26 else:
26 form = SettingsForm(initial={'theme': selected_theme},
27 form = SettingsForm(initial={'theme': selected_theme},
27 error_class=PlainErrorList)
28 error_class=PlainErrorList)
28
29
29 context[CONTEXT_FORM] = form
30 context[CONTEXT_FORM] = form
30
31
31 return render(request, 'boards/settings.html', context)
32 return render(request, 'boards/settings.html', context)
32
33
33 def post(self, request):
34 def post(self, request):
34 user = utils.get_user(request)
35 settings_manager = SettingsManager(request.session)
35 is_moderator = user.is_moderator()
36 is_moderator = settings_manager.has_permission(PERMISSION_MODERATE)
36
37
37 with transaction.atomic():
38 with transaction.atomic():
38 if is_moderator:
39 if is_moderator:
39 form = ModeratorSettingsForm(request.POST,
40 form = ModeratorSettingsForm(request.POST,
40 error_class=PlainErrorList)
41 error_class=PlainErrorList)
41 else:
42 else:
42 form = SettingsForm(request.POST, error_class=PlainErrorList)
43 form = SettingsForm(request.POST, error_class=PlainErrorList)
43
44
44 if form.is_valid():
45 if form.is_valid():
45 selected_theme = form.cleaned_data['theme']
46 selected_theme = form.cleaned_data['theme']
46
47
47 user.save_setting('theme', selected_theme)
48 settings_manager.set_theme(selected_theme)
48
49
49 if is_moderator:
50 if is_moderator:
50 moderate = form.cleaned_data['moderate']
51 moderate = form.cleaned_data['moderate']
51 user.save_setting(SETTING_MODERATE, moderate)
52 if moderate == 'True':
53 settings_manager.add_permission(PERMISSION_MODERATE)
52
54
53 return redirect('settings')
55 return redirect('settings')
@@ -1,87 +1,91 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 import utils
3 from boards.abstracts.settingsmanager import SettingsManager
3 from boards.models import Tag, Post
4 from boards.models import Tag, Post
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
8 __author__ = 'neko259'
9 __author__ = 'neko259'
9
10
10
11
11 class TagView(AllThreadsView, DispatcherMixin, RedirectNextMixin):
12 class TagView(AllThreadsView, DispatcherMixin, RedirectNextMixin):
12
13
13 tag_name = None
14 tag_name = None
14
15
15 def get_threads(self):
16 def get_threads(self):
16 tag = get_object_or_404(Tag, name=self.tag_name)
17 tag = get_object_or_404(Tag, name=self.tag_name)
17
18
18 return tag.threads.all().order_by('-bump_time')
19 return tag.threads.all().order_by('-bump_time')
19
20
20 def get_context_data(self, **kwargs):
21 def get_context_data(self, **kwargs):
21 context = super(TagView, self).get_context_data(**kwargs)
22 context = super(TagView, self).get_context_data(**kwargs)
22
23
24 settings_manager = SettingsManager(kwargs['request'].session)
25
23 tag = get_object_or_404(Tag, name=self.tag_name)
26 tag = get_object_or_404(Tag, name=self.tag_name)
24 context['tag'] = tag
27 context['tag'] = tag
25
28
29 context['fav_tags'] = settings_manager.get_fav_tags()
30 context['hidden_tags'] = settings_manager.get_hidden_tags()
31
26 return context
32 return context
27
33
28 def get(self, request, tag_name, page=DEFAULT_PAGE, form=None):
34 def get(self, request, tag_name, page=DEFAULT_PAGE, form=None):
29 self.tag_name = tag_name
35 self.tag_name = tag_name
30
36
31 dispatch_result = self.dispatch_method(request)
37 dispatch_result = self.dispatch_method(request)
32 if dispatch_result:
38 if dispatch_result:
33 return dispatch_result
39 return dispatch_result
34 else:
40 else:
35 return super(TagView, self).get(request, page, form)
41 return super(TagView, self).get(request, page, form)
36
42
37 def post(self, request, tag_name, page=DEFAULT_PAGE):
43 def post(self, request, tag_name, page=DEFAULT_PAGE):
38 form = ThreadForm(request.POST, request.FILES,
44 form = ThreadForm(request.POST, request.FILES,
39 error_class=PlainErrorList)
45 error_class=PlainErrorList)
40 form.session = request.session
46 form.session = request.session
41
47
42 if form.is_valid():
48 if form.is_valid():
43 return self.create_thread(request, form)
49 return self.create_thread(request, form)
44 if form.need_to_ban:
50 if form.need_to_ban:
45 # Ban user because he is suspected to be a bot
51 # Ban user because he is suspected to be a bot
46 self._ban_current_user(request)
52 self._ban_current_user(request)
47
53
48 return self.get(request, tag_name, page, form)
54 return self.get(request, tag_name, page, form)
49
55
50 def subscribe(self, request):
56 def subscribe(self, request):
51 user = utils.get_user(request)
52 tag = get_object_or_404(Tag, name=self.tag_name)
57 tag = get_object_or_404(Tag, name=self.tag_name)
53
58
54 if not tag in user.fav_tags.all():
59 settings_manager = SettingsManager(request.session)
55 user.add_tag(tag)
60 settings_manager.add_fav_tag(tag)
56
61
57 return self.redirect_to_next(request)
62 return self.redirect_to_next(request)
58
63
59 def unsubscribe(self, request):
64 def unsubscribe(self, request):
60 user = utils.get_user(request)
61 tag = get_object_or_404(Tag, name=self.tag_name)
65 tag = get_object_or_404(Tag, name=self.tag_name)
62
66
63 if tag in user.fav_tags.all():
67 settings_manager = SettingsManager(request.session)
64 user.remove_tag(tag)
68 settings_manager.del_fav_tag(tag)
65
69
66 return self.redirect_to_next(request)
70 return self.redirect_to_next(request)
67
71
68 def hide(self, request):
72 def hide(self, request):
69 """
73 """
70 Adds tag to user's hidden tags. Threads with this tag will not be
74 Adds tag to user's hidden tags. Threads with this tag will not be
71 shown.
75 shown.
72 """
76 """
73
77
74 user = utils.get_user(request)
75 tag = get_object_or_404(Tag, name=self.tag_name)
78 tag = get_object_or_404(Tag, name=self.tag_name)
76
79
77 user.hide_tag(tag)
80 settings_manager = SettingsManager(request.session)
81 settings_manager.add_hidden_tag(tag)
78
82
79 def unhide(self, request):
83 def unhide(self, request):
80 """
84 """
81 Removed tag from user's hidden tags.
85 Removed tag from user's hidden tags.
82 """
86 """
83
87
84 user = utils.get_user(request)
85 tag = get_object_or_404(Tag, name=self.tag_name)
88 tag = get_object_or_404(Tag, name=self.tag_name)
86
89
87 user.unhide_tag(tag)
90 settings_manager = SettingsManager(request.session)
91 settings_manager.del_hidden_tag(tag)
@@ -1,144 +1,142 b''
1 from django.core.urlresolvers import reverse
1 from django.core.urlresolvers import reverse
2 from django.db import transaction
2 from django.db import transaction
3 from django.http import Http404
3 from django.http import Http404
4 from django.shortcuts import get_object_or_404, render, redirect
4 from django.shortcuts import get_object_or_404, render, redirect
5 from django.views.generic.edit import FormMixin
5 from django.views.generic.edit import FormMixin
6
6
7 from boards import utils, settings
7 from boards import utils, settings
8 from boards.forms import PostForm, PlainErrorList
8 from boards.forms import PostForm, PlainErrorList
9 from boards.models import Post, Ban
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, CONTEXT_FORM
11 from boards.views.base import BaseBoardView, CONTEXT_FORM
12 from boards.views.posting_mixin import PostMixin
12 from boards.views.posting_mixin import PostMixin
13
13
14 TEMPLATE_GALLERY = 'boards/thread_gallery.html'
14 TEMPLATE_GALLERY = 'boards/thread_gallery.html'
15 TEMPLATE_NORMAL = 'boards/thread.html'
15 TEMPLATE_NORMAL = 'boards/thread.html'
16
16
17 CONTEXT_POSTS = 'posts'
17 CONTEXT_POSTS = 'posts'
18 CONTEXT_OP = 'opening_post'
18 CONTEXT_OP = 'opening_post'
19 CONTEXT_BUMPLIMIT_PRG = 'bumplimit_progress'
19 CONTEXT_BUMPLIMIT_PRG = 'bumplimit_progress'
20 CONTEXT_POSTS_LEFT = 'posts_left'
20 CONTEXT_POSTS_LEFT = 'posts_left'
21 CONTEXT_LASTUPDATE = "last_update"
21 CONTEXT_LASTUPDATE = "last_update"
22 CONTEXT_MAX_REPLIES = 'max_replies'
22 CONTEXT_MAX_REPLIES = 'max_replies'
23 CONTEXT_THREAD = 'thread'
23 CONTEXT_THREAD = 'thread'
24 CONTEXT_BUMPABLE = 'bumpable'
24 CONTEXT_BUMPABLE = 'bumpable'
25
25
26 FORM_TITLE = 'title'
26 FORM_TITLE = 'title'
27 FORM_TEXT = 'text'
27 FORM_TEXT = 'text'
28 FORM_IMAGE = 'image'
28 FORM_IMAGE = 'image'
29
29
30 MODE_GALLERY = 'gallery'
30 MODE_GALLERY = 'gallery'
31 MODE_NORMAL = 'normal'
31 MODE_NORMAL = 'normal'
32
32
33
33
34 class ThreadView(BaseBoardView, PostMixin, FormMixin):
34 class ThreadView(BaseBoardView, PostMixin, FormMixin):
35
35
36 def get(self, request, post_id, mode=MODE_NORMAL, form=None):
36 def get(self, request, post_id, mode=MODE_NORMAL, form=None):
37 try:
37 try:
38 opening_post = Post.objects.filter(id=post_id).only('thread_new')[0]
38 opening_post = Post.objects.filter(id=post_id).only('thread_new')[0]
39 except IndexError:
39 except IndexError:
40 raise Http404
40 raise Http404
41
41
42 # If this is not OP, don't show it as it is
42 # If this is not OP, don't show it as it is
43 if not opening_post or not opening_post.is_opening():
43 if not opening_post or not opening_post.is_opening():
44 raise Http404
44 raise Http404
45
45
46 if not form:
46 if not form:
47 form = PostForm(error_class=PlainErrorList)
47 form = PostForm(error_class=PlainErrorList)
48
48
49 thread_to_show = opening_post.get_thread()
49 thread_to_show = opening_post.get_thread()
50
50
51 context = self.get_context_data(request=request)
51 context = self.get_context_data(request=request)
52
52
53 context[CONTEXT_FORM] = form
53 context[CONTEXT_FORM] = form
54 context[CONTEXT_LASTUPDATE] = utils.datetime_to_epoch(
54 context[CONTEXT_LASTUPDATE] = utils.datetime_to_epoch(
55 thread_to_show.last_edit_time)
55 thread_to_show.last_edit_time)
56 context[CONTEXT_THREAD] = thread_to_show
56 context[CONTEXT_THREAD] = thread_to_show
57 context[CONTEXT_MAX_REPLIES] = settings.MAX_POSTS_PER_THREAD
57 context[CONTEXT_MAX_REPLIES] = settings.MAX_POSTS_PER_THREAD
58
58
59 if MODE_NORMAL == mode:
59 if MODE_NORMAL == mode:
60 bumpable = thread_to_show.can_bump()
60 bumpable = thread_to_show.can_bump()
61 context[CONTEXT_BUMPABLE] = bumpable
61 context[CONTEXT_BUMPABLE] = bumpable
62 if bumpable:
62 if bumpable:
63 left_posts = settings.MAX_POSTS_PER_THREAD \
63 left_posts = settings.MAX_POSTS_PER_THREAD \
64 - thread_to_show.get_reply_count()
64 - thread_to_show.get_reply_count()
65 context[CONTEXT_POSTS_LEFT] = left_posts
65 context[CONTEXT_POSTS_LEFT] = left_posts
66 context[CONTEXT_BUMPLIMIT_PRG] = str(
66 context[CONTEXT_BUMPLIMIT_PRG] = str(
67 float(left_posts) / settings.MAX_POSTS_PER_THREAD * 100)
67 float(left_posts) / settings.MAX_POSTS_PER_THREAD * 100)
68
68
69 context[CONTEXT_OP] = opening_post
69 context[CONTEXT_OP] = opening_post
70
70
71 document = TEMPLATE_NORMAL
71 document = TEMPLATE_NORMAL
72 elif MODE_GALLERY == mode:
72 elif MODE_GALLERY == mode:
73 context[CONTEXT_POSTS] = thread_to_show.get_replies_with_images(
73 context[CONTEXT_POSTS] = thread_to_show.get_replies_with_images(
74 view_fields_only=True)
74 view_fields_only=True)
75
75
76 document = TEMPLATE_GALLERY
76 document = TEMPLATE_GALLERY
77 else:
77 else:
78 raise Http404
78 raise Http404
79
79
80 return render(request, document, context)
80 return render(request, document, context)
81
81
82 def post(self, request, post_id, mode=MODE_NORMAL):
82 def post(self, request, post_id, mode=MODE_NORMAL):
83 opening_post = get_object_or_404(Post, id=post_id)
83 opening_post = get_object_or_404(Post, id=post_id)
84
84
85 # If this is not OP, don't show it as it is
85 # If this is not OP, don't show it as it is
86 if not opening_post.is_opening():
86 if not opening_post.is_opening():
87 raise Http404
87 raise Http404
88
88
89 if not opening_post.get_thread().archived:
89 if not opening_post.get_thread().archived:
90 form = PostForm(request.POST, request.FILES,
90 form = PostForm(request.POST, request.FILES,
91 error_class=PlainErrorList)
91 error_class=PlainErrorList)
92 form.session = request.session
92 form.session = request.session
93
93
94 if form.is_valid():
94 if form.is_valid():
95 return self.new_post(request, form, opening_post)
95 return self.new_post(request, form, opening_post)
96 if form.need_to_ban:
96 if form.need_to_ban:
97 # Ban user because he is suspected to be a bot
97 # Ban user because he is suspected to be a bot
98 self._ban_current_user(request)
98 self._ban_current_user(request)
99
99
100 return self.get(request, post_id, mode, form)
100 return self.get(request, post_id, mode, form)
101
101
102 @transaction.atomic
102 @transaction.atomic
103 def new_post(self, request, form, opening_post=None, html_response=True):
103 def new_post(self, request, form, opening_post=None, html_response=True):
104 """Add a new post (in thread or as a reply)."""
104 """Add a new post (in thread or as a reply)."""
105
105
106 ip = utils.get_client_ip(request)
106 ip = utils.get_client_ip(request)
107 is_banned = Ban.objects.filter(ip=ip).exists()
107 is_banned = Ban.objects.filter(ip=ip).exists()
108
108
109 if is_banned:
109 if is_banned:
110 if html_response:
110 if html_response:
111 return redirect(BannedView().as_view())
111 return redirect(BannedView().as_view())
112 else:
112 else:
113 return None
113 return None
114
114
115 data = form.cleaned_data
115 data = form.cleaned_data
116
116
117 title = data[FORM_TITLE]
117 title = data[FORM_TITLE]
118 text = data[FORM_TEXT]
118 text = data[FORM_TEXT]
119
119
120 text = self._remove_invalid_links(text)
120 text = self._remove_invalid_links(text)
121
121
122 if FORM_IMAGE in data.keys():
122 if FORM_IMAGE in data.keys():
123 image = data[FORM_IMAGE]
123 image = data[FORM_IMAGE]
124 else:
124 else:
125 image = None
125 image = None
126
126
127 tags = []
127 tags = []
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
138 if html_response:
136 if html_response:
139 if opening_post:
137 if opening_post:
140 return redirect(
138 return redirect(
141 reverse('thread', kwargs={'post_id': thread_to_show})
139 reverse('thread', kwargs={'post_id': thread_to_show})
142 + '#' + str(post.id))
140 + '#' + str(post.id))
143 else:
141 else:
144 return post
142 return post
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