diff --git a/boards/abstracts/settingsmanager.py b/boards/abstracts/settingsmanager.py
new file mode 100644
--- /dev/null
+++ b/boards/abstracts/settingsmanager.py
@@ -0,0 +1,143 @@
+from django.shortcuts import get_object_or_404
+from boards.models import Tag
+
+__author__ = 'neko259'
+
+SESSION_SETTING = 'setting'
+
+PERMISSION_MODERATE = 'moderator'
+
+SETTING_THEME = 'theme'
+SETTING_FAVORITE_TAGS = 'favorite_tags'
+SETTING_HIDDEN_TAGS = 'hidden_tags'
+SETTING_PERMISSIONS = 'permissions'
+
+DEFAULT_THEME = 'md'
+
+
+def get_settings_manager(request):
+ """
+ Get settings manager based on the request object. Currently only
+ session-based manager is supported. In the future, cookie-based or
+ database-based managers could be implemented.
+ """
+ return SessionSettingsManager(request.session)
+
+
+class SettingsManager:
+ """
+ Base settings manager class. get_setting and set_setting methods should
+ be overriden.
+ """
+ def __init__(self):
+ pass
+
+ def get_theme(self):
+ theme = self.get_setting(SETTING_THEME)
+ if not theme:
+ theme = DEFAULT_THEME
+ self.set_setting(SETTING_THEME, theme)
+
+ return theme
+
+ def set_theme(self, theme):
+ self.set_setting(SETTING_THEME, theme)
+
+ def has_permission(self, permission):
+ permissions = self.get_setting(SETTING_PERMISSIONS)
+ if permissions:
+ return permission in permissions
+ else:
+ return False
+
+ def get_setting(self, setting):
+ pass
+
+ def set_setting(self, setting, value):
+ pass
+
+ def add_permission(self, permission):
+ permissions = self.get_setting(SETTING_PERMISSIONS)
+ if not permissions:
+ permissions = [permission]
+ else:
+ permissions.append(permission)
+ self.set_setting(SETTING_PERMISSIONS, permissions)
+
+ def del_permission(self, permission):
+ permissions = self.get_setting(SETTING_PERMISSIONS)
+ if not permissions:
+ permissions = []
+ else:
+ permissions.remove(permission)
+ self.set_setting(SETTING_PERMISSIONS, permissions)
+
+ def get_fav_tags(self):
+ tag_names = self.get_setting(SETTING_FAVORITE_TAGS)
+ tags = []
+ if tag_names:
+ for tag_name in tag_names:
+ tag = get_object_or_404(Tag, name=tag_name)
+ tags.append(tag)
+
+ return tags
+
+ def add_fav_tag(self, tag):
+ tags = self.get_setting(SETTING_FAVORITE_TAGS)
+ if not tags:
+ tags = [tag.name]
+ else:
+ if not tag.name in tags:
+ tags.append(tag.name)
+ self.set_setting(SETTING_FAVORITE_TAGS, tags)
+
+ def del_fav_tag(self, tag):
+ tags = self.get_setting(SETTING_FAVORITE_TAGS)
+ if tag.name in tags:
+ tags.remove(tag.name)
+ self.set_setting(SETTING_FAVORITE_TAGS, tags)
+
+ def get_hidden_tags(self):
+ tag_names = self.get_setting(SETTING_HIDDEN_TAGS)
+ tags = []
+ if tag_names:
+ for tag_name in tag_names:
+ tag = get_object_or_404(Tag, name=tag_name)
+ tags.append(tag)
+
+ return tags
+
+ def add_hidden_tag(self, tag):
+ tags = self.get_setting(SETTING_HIDDEN_TAGS)
+ if not tags:
+ tags = [tag.name]
+ else:
+ if not tag.name in tags:
+ tags.append(tag.name)
+ self.set_setting(SETTING_HIDDEN_TAGS, tags)
+
+ def del_hidden_tag(self, tag):
+ tags = self.get_setting(SETTING_HIDDEN_TAGS)
+ if tag.name in tags:
+ tags.remove(tag.name)
+ self.set_setting(SETTING_HIDDEN_TAGS, tags)
+
+
+class SessionSettingsManager(SettingsManager):
+ """
+ Session-based settings manager. All settings are saved to the user's
+ session.
+ """
+ def __init__(self, session):
+ SettingsManager.__init__(self)
+ self.session = session
+
+ def get_setting(self, setting):
+ if setting in self.session:
+ return self.session[setting]
+ else:
+ return None
+
+ def set_setting(self, setting, value):
+ self.session[setting] = value
+
diff --git a/boards/admin.py b/boards/admin.py
--- a/boards/admin.py
+++ b/boards/admin.py
@@ -1,5 +1,5 @@
from django.contrib import admin
-from boards.models import Post, Tag, User, Ban, Thread
+from boards.models import Post, Tag, Ban, Thread
class PostAdmin(admin.ModelAdmin):
@@ -11,15 +11,7 @@ class PostAdmin(admin.ModelAdmin):
class TagAdmin(admin.ModelAdmin):
- list_display = ('name', 'linked')
- list_filter = ('linked',)
-
-
-class UserAdmin(admin.ModelAdmin):
-
- list_display = ('user_id', 'rank')
- search_fields = ('user_id',)
-
+ list_display = ('name',)
class ThreadAdmin(admin.ModelAdmin):
@@ -35,6 +27,5 @@ class ThreadAdmin(admin.ModelAdmin):
admin.site.register(Post, PostAdmin)
admin.site.register(Tag, TagAdmin)
-admin.site.register(User, UserAdmin)
admin.site.register(Ban)
admin.site.register(Thread, ThreadAdmin)
diff --git a/boards/context_processors.py b/boards/context_processors.py
--- a/boards/context_processors.py
+++ b/boards/context_processors.py
@@ -1,8 +1,10 @@
+from boards.abstracts.settingsmanager import PERMISSION_MODERATE, \
+ get_settings_manager
+
__author__ = 'neko259'
-from boards import utils, settings
+from boards import settings
from boards.models import Post
-from boards.models.post import SETTING_MODERATE
CONTEXT_SITE_NAME = 'site_name'
CONTEXT_VERSION = 'version'
@@ -17,21 +19,17 @@ CONTEXT_USER = 'user'
def user_and_ui_processor(request):
context = {}
- user = utils.get_user(request)
- context[CONTEXT_USER] = user
- context[CONTEXT_TAGS] = user.fav_tags.all()
context[CONTEXT_PPD] = float(Post.objects.get_posts_per_day())
- theme = utils.get_theme(request, user)
+ settings_manager = get_settings_manager(request)
+ context[CONTEXT_TAGS] = settings_manager.get_fav_tags()
+ theme = settings_manager.get_theme()
context[CONTEXT_THEME] = theme
context[CONTEXT_THEME_CSS] = 'css/' + theme + '/base_page.css'
# This shows the moderator panel
- moderate = user.get_setting(SETTING_MODERATE)
- if moderate == 'True':
- context[CONTEXT_MODERATOR] = user.is_moderator()
- else:
- context[CONTEXT_MODERATOR] = False
+ moderate = settings_manager.has_permission(PERMISSION_MODERATE)
+ context[CONTEXT_MODERATOR] = moderate
context[CONTEXT_VERSION] = settings.VERSION
context[CONTEXT_SITE_NAME] = settings.SITE_NAME
diff --git a/boards/forms.py b/boards/forms.py
--- a/boards/forms.py
+++ b/boards/forms.py
@@ -9,7 +9,7 @@ from django.utils.translation import uge
from boards.mdx_neboard import formatters
from boards.models.post import TITLE_MAX_LENGTH
-from boards.models import User, PostImage
+from boards.models import PostImage
from neboard import settings
from boards import utils
import boards.settings as board_settings
@@ -20,8 +20,7 @@ ATTRIBUTE_PLACEHOLDER = 'placeholder'
LAST_POST_TIME = 'last_post_time'
LAST_LOGIN_TIME = 'last_login_time'
-TEXT_PLACEHOLDER = _('''Type message here. You can reply to message >>123 like
- this. 2 new lines are required to start new paragraph.''')
+TEXT_PLACEHOLDER = _('''Type message here. Use formatting panel for more advanced usage.''')
TAGS_PLACEHOLDER = _('tag1 several_words_tag')
ERROR_IMAGE_DUPLICATE = _('Such image was already posted')
@@ -187,11 +186,7 @@ class PostForm(NeboardForm):
if not 'user_id' in self.session:
return
- user = User.objects.get(id=self.session['user_id'])
- if user.is_veteran():
- posting_delay = VETERAN_POSTING_DELAY
- else:
- posting_delay = settings.POSTING_DELAY
+ posting_delay = settings.POSTING_DELAY
if board_settings.LIMIT_POSTING_SPEED and LAST_POST_TIME in \
self.session:
@@ -282,57 +277,6 @@ class SettingsForm(NeboardForm):
label=_('Theme'))
-class ModeratorSettingsForm(SettingsForm):
-
- moderate = forms.BooleanField(required=False, label=_('Enable moderation '
- 'panel'))
-
-
-class LoginForm(NeboardForm):
-
- user_id = forms.CharField()
-
- session = None
-
- def clean_user_id(self):
- user_id = self.cleaned_data['user_id']
- if user_id:
- users = User.objects.filter(user_id=user_id)
- if len(users) == 0:
- raise forms.ValidationError(_('No such user found'))
-
- return user_id
-
- def _validate_login_speed(self):
- can_post = True
-
- if LAST_LOGIN_TIME in self.session:
- now = time.time()
- last_login_time = self.session[LAST_LOGIN_TIME]
-
- current_delay = int(now - last_login_time)
-
- if current_delay < board_settings.LOGIN_TIMEOUT:
- error_message = _('Wait %s minutes after last login') % str(
- (board_settings.LOGIN_TIMEOUT - current_delay) / 60)
- self._errors['user_id'] = self.error_class([error_message])
-
- can_post = False
-
- if can_post:
- self.session[LAST_LOGIN_TIME] = time.time()
-
- def clean(self):
- if not self.session:
- raise forms.ValidationError('Humans have sessions')
-
- self._validate_login_speed()
-
- cleaned_data = super(LoginForm, self).clean()
-
- return cleaned_data
-
-
class AddTagForm(NeboardForm):
tag = forms.CharField(max_length=TAG_MAX_LENGTH, label=LABEL_TAG)
@@ -354,4 +298,44 @@ class AddTagForm(NeboardForm):
class SearchForm(NeboardForm):
- query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
\ No newline at end of file
+ query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
+
+
+class LoginForm(NeboardForm):
+
+ password = forms.CharField()
+
+ session = None
+
+ def clean_password(self):
+ password = self.cleaned_data['password']
+ if board_settings.MASTER_PASSWORD != password:
+ raise forms.ValidationError(_('Invalid master password'))
+
+ return password
+
+ def _validate_login_speed(self):
+ can_post = True
+
+ if LAST_LOGIN_TIME in self.session:
+ now = time.time()
+ last_login_time = self.session[LAST_LOGIN_TIME]
+
+ current_delay = int(now - last_login_time)
+
+ if current_delay < board_settings.LOGIN_TIMEOUT:
+ error_message = _('Wait %s minutes after last login') % str(
+ (board_settings.LOGIN_TIMEOUT - current_delay) / 60)
+ self._errors['password'] = self.error_class([error_message])
+
+ can_post = False
+
+ if can_post:
+ self.session[LAST_LOGIN_TIME] = time.time()
+
+ def clean(self):
+ self._validate_login_speed()
+
+ cleaned_data = super(LoginForm, self).clean()
+
+ return cleaned_data
diff --git a/boards/locale/ru/LC_MESSAGES/django.mo b/boards/locale/ru/LC_MESSAGES/django.mo
index b4d12e2f1e9cc3c1f276de818a34ae547f18bb65..1f70c1ff6647291211289c5624ab67fe2bfbb633
GIT binary patch
literal 6397
zc$|$_Yit}>6~54yABi2iPLtA>-cV8!+TEJ0za?U7lEsQZ)ltVRsg>XB$s{&
zl)#??H;{kYUOj}?0q?J1KbwK8fnC5F;AblMe6E81@GIEgXMnZ9=d|1lz>fi6*Lr@W
z<$h7Y{{3E`|D#^N1FQo6RonZIwo_fn@z+)I`u0k;b9W`nZ>i+-=}OkOtCHi~r?C^5
z20mHIb@`IkR|M7rr-65of0gX#pR}I818)HSyOQG}V`QfmxP$6c#r`~|_n!dX3;c8y
z>lxAS&jZ&3U)1aGRB>J=fxCgf27)T$u4=A_1bzgVsb>GoYWDwFHTyMQ&2i0Cb3VVX
z_kRp*0RF6+<9l1ny{q-S2fPiq3S_wQ0U_>L#dZGGYR>21Rx?j-sNsC9sbRm9z`Kce
zz$CE0hV^{8hW&iAhVv8Fa9qCwt^wWxrH=yF*K(bVTIN@_mi3;lW&5wyvc4<8y}+LU
zA0!?^$xdJ^@Z-R$96UY?*VSBV_rX5$LF2_CV(&1aXdc&
z-b(zdWB>nI$9bx(XS}nX?QE&%zB*jb`T2szv3j=m)q3XDH|sf$@9Fcu)bIbS*Z;un
z4$=>!gVfi+hk;+emF@rhR`&M~z-Hjh4ZJ?kz~^%fT;~&d|4akN{Vm{W;za}V;gPj$
zcP}sl>{!e3KEIabi@*nnKWkat>)Fr8*R!4D8b^Wm178I;Qr`hVM-?;u(Tt;hqMq1_>yvtar$&mY
zT0axNwh6A&0ljypM(WWA^)tV-;2P5(_0H}3
zc^BO>rYQrXUu3db84P%qVfrFd2nJlw7kdmxwssLXmMQkQS%_N41F_f02YmyE95k}F
zX#}?G$fJgxh0Iv<&H{#%o#q8$h_hE$6U`8tpT?CI8|+UCkuIG$x2JnW;;*Iz?Fu~yFOmJ{kB7Q
zw-AU9iaSqEcerw(6jPGc^dOJA$QUoVP|`Y%bS91iGqNyS^Q`-jL`v
zkdki8@cIS-L0~)mzUUrskEwEWKW*ppsQJcCeQ+b#>`RXoCnb5L^^s=ZvLwp8{9bhD
zQSvlS=JO`bQRZ+*!c7BK^Dcw#
z9OmqF4>^HxT=c+CT&SmmLyE^(`s%jKA=Ap*Ih*L)J0$(Q(MKWlT*X7baZ@}kEMvKHU}AH_p?h~Xt^LaSLx
zdB_K4h$pIEu;Ex)+CW}Bl))S{oIdJQI+7HJ^5O}@4oGdzcF@Df>QSP8mRO!uY>owd
ztIu`Js{|bm*+lZ^#zGmGx0dmis_e@U75WHGW%QP<1GifTRS_ds4u)A
z(eC%#W@1mF-%s?oX<60P*^^MsibIKJH50a`8n-1<4<)vy@R)9Fyf5`YDh0vBLG+?u
zk~`dzIhbhgOf+ZYqn+vY*84uy+0jx(<0aAK8IGT&WTfT3Lv3xcBh%g@ceJ!;THAJK
znwt-{ba(HBsMB9SsEHoS$fadlrepsha(PEv+wQELbiMwPOj>${s`l3QmQsX`$y5~;
z0bP+GBBfQ6ZOLa1+u5#I=37D2p`Lw-hf8wg*ip+%v^agPiPER#!@YJ;)s-zErHOqY
zo}ZRZUM>Bm2ewOoY}({(Y)myZHOhPMmGqd}(A3x{cgd7YtM$&Nt?{cJP21>Eok;Cy
zOvz`TABY5xxYy%3OT#QtZ2g={a(9mi&OB
zX$YwhCMjk_jA&(eIT~fB#wkj?BiBchBK%q$1UWNB2@2
z$pcG8r4d^RoG&Vp%u#7xhGQp`H{&ZYjVi5}G^wLgZJ@ZK35J~D$4Ph*2S%}_iV510
z%Zn;~Uo2(xLUf85asi&|$BV3O0jsuFd*G`&HJ6(NdjZ(OzJXxXCdQ4g}1daB{6
zsg_iBrg@4jkBaEjvMuF1&8TUr2<4-weE~yhmhoE}N>^6sHlAKfDh`#svfp*E6MIK>
zm2qB4GdIFX&cyvPI-`WpfD)q;Ii+Igh}29j=~5g(^Q(Y(bd8mM6JA5q8On)f57km-
zWQHen@p4PDZD=y(iV#*gyK{&8uvWP`#Go??&09{)CVvBvDO&O8ttusDV+Y5uBL(-`bYA
zsnw5(3DS9iDmSkbQx_F^mZ=zu(OLSx0^*vaW&$O+4Y>(d_&KJ&7vo^3+e+Op)0Cq2Ci<+@G&9$cmZkrh2=xo?v^sEw
zs6mIW8EPwRDR%OYm3bs_PEk;OLIjVCMm;u;kZVVj_rq$m5z#11r;cbAjf?*RSUp(e
diff --git a/boards/locale/ru/LC_MESSAGES/django.po b/boards/locale/ru/LC_MESSAGES/django.po
--- a/boards/locale/ru/LC_MESSAGES/django.po
+++ b/boards/locale/ru/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-06-29 13:46+0300\n"
+"POT-Creation-Date: 2014-07-20 20:11+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -18,56 +18,53 @@ msgstr ""
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
-#: authors.py:5
+#: authors.py:9
msgid "author"
msgstr "автор"
-#: authors.py:6
+#: authors.py:10
msgid "developer"
msgstr "разработчик"
-#: authors.py:7
+#: authors.py:11
msgid "javascript developer"
msgstr "разработчик javascript"
-#: authors.py:8
+#: authors.py:12
msgid "designer"
msgstr "дизайнер"
#: forms.py:23
-msgid ""
-"Type message here. You can reply to message >>123 like\n"
-" this. 2 new lines are required to start new paragraph."
+msgid "Type message here. Use formatting panel for more advanced usage."
msgstr ""
-"Введите сообщение здесь. Вы можете ответить на сообщение >>123 вот так. 2 "
-"переноса строки обязательны для создания нового абзаца."
+"Вводите сообщение сюда. Используйте панель для более сложного форматирования."
-#: forms.py:25
+#: forms.py:24
msgid "tag1 several_words_tag"
msgstr "тег1 тег_из_нескольких_слов"
-#: forms.py:27
+#: forms.py:26
msgid "Such image was already posted"
msgstr "Такое изображение уже было загружено"
-#: forms.py:29
+#: forms.py:28
msgid "Title"
msgstr "Заголовок"
-#: forms.py:30
+#: forms.py:29
msgid "Text"
msgstr "Текст"
-#: forms.py:31
+#: forms.py:30
msgid "Tag"
msgstr "Тег"
-#: forms.py:32 templates/boards/base.html:50 templates/search/search.html:9
+#: forms.py:31 templates/boards/base.html:54 templates/search/search.html:9
#: templates/search/search.html.py:13
msgid "Search"
msgstr "Поиск"
-#: forms.py:109
+#: forms.py:108
msgid "Image"
msgstr "Изображение"
@@ -94,36 +91,32 @@ msgstr "Изображение должно быть менее %s байт"
msgid "Either text or image must be entered."
msgstr "Текст или картинка должны быть введены."
-#: forms.py:202
+#: forms.py:199
#, python-format
msgid "Wait %s seconds after last posting"
msgstr "Подождите %s секунд после последнего постинга"
-#: forms.py:218 templates/boards/tags.html:7 templates/boards/rss/post.html:10
+#: forms.py:215 templates/boards/tags.html:7 templates/boards/rss/post.html:10
msgid "Tags"
msgstr "Теги"
-#: forms.py:225 forms.py:344
+#: forms.py:222 forms.py:290
msgid "Inappropriate characters in tags."
msgstr "Недопустимые символы в тегах."
-#: forms.py:253 forms.py:274
+#: forms.py:250 forms.py:271
msgid "Captcha validation failed"
msgstr "Проверка капчи провалена"
-#: forms.py:280
+#: forms.py:277
msgid "Theme"
msgstr "Тема"
-#: forms.py:285
-msgid "Enable moderation panel"
-msgstr "Включить панель модерации"
+#: forms.py:313
+msgid "Invalid master password"
+msgstr "Неверный мастер-пароль"
-#: forms.py:300
-msgid "No such user found"
-msgstr "Данный пользователь не найден"
-
-#: forms.py:314
+#: forms.py:327
#, python-format
msgid "Wait %s minutes after last login"
msgstr "Подождите %s минут после последнего входа"
@@ -168,17 +161,21 @@ msgstr "Управление тегами"
msgid "Settings"
msgstr "Настройки"
-#: templates/boards/base.html:49 templates/boards/login.html:6
+#: templates/boards/base.html:50
+msgid "Logout"
+msgstr "Выход"
+
+#: templates/boards/base.html:52 templates/boards/login.html:6
#: templates/boards/login.html.py:16
msgid "Login"
msgstr "Вход"
-#: templates/boards/base.html:52
+#: templates/boards/base.html:56
#, python-format
msgid "Speed: %(ppd)s posts per day"
msgstr "Скорость: %(ppd)s сообщений в день"
-#: templates/boards/base.html:54
+#: templates/boards/base.html:58
msgid "Up"
msgstr "Вверх"
@@ -186,7 +183,7 @@ msgstr "Вверх"
msgid "Insert your user id above"
msgstr "Вставьте свой ID пользователя выше"
-#: templates/boards/post.html:21 templates/boards/staticpages/help.html:19
+#: templates/boards/post.html:21 templates/boards/staticpages/help.html:17
msgid "Quote"
msgstr "Цитата"
@@ -216,8 +213,8 @@ msgstr "Ответы"
#: templates/boards/post.html:86 templates/boards/thread.html:88
#: templates/boards/thread_gallery.html:61
-msgid "replies"
-msgstr "ответов"
+msgid "messages"
+msgstr "сообщений"
#: templates/boards/post.html:87 templates/boards/thread.html:89
#: templates/boards/thread_gallery.html:62
@@ -249,7 +246,7 @@ msgstr "Предыдущая страница"
msgid "Skipped %(count)s replies. Open thread to see all replies."
msgstr "Пропущено %(count)s ответов. Откройте тред, чтобы увидеть все ответы."
-#: templates/boards/posting_general.html:121 templates/search/search.html:35
+#: templates/boards/posting_general.html:121 templates/search/search.html:33
msgid "Next page"
msgstr "Следующая страница"
@@ -278,35 +275,19 @@ msgstr "Синтаксис текста"
msgid "Pages:"
msgstr "Страницы: "
-#: templates/boards/settings.html:14
-msgid "User:"
-msgstr "Пользователь:"
-
-#: templates/boards/settings.html:16
+#: templates/boards/settings.html:15
msgid "You are moderator."
msgstr "Вы модератор."
#: templates/boards/settings.html:19
-msgid "You are veteran."
-msgstr "Вы ветеран."
-
-#: templates/boards/settings.html:22
-msgid "Posts:"
-msgstr "Сообщений:"
-
-#: templates/boards/settings.html:23
-msgid "First access:"
-msgstr "Первый доступ:"
-
-#: templates/boards/settings.html:25
-msgid "Last access:"
-msgstr "Последний доступ: "
-
-#: templates/boards/settings.html:29
msgid "Hidden tags:"
msgstr "Скрытые теги:"
-#: templates/boards/settings.html:44
+#: templates/boards/settings.html:26
+msgid "No hidden tags."
+msgstr "Нет скрытых тегов."
+
+#: templates/boards/settings.html:35
msgid "Save"
msgstr "Сохранить"
@@ -356,37 +337,25 @@ msgid "Syntax"
msgstr "Синтаксис"
#: templates/boards/staticpages/help.html:11
-msgid "2 line breaks for a new line."
-msgstr "2 перевода строки создают новый абзац."
-
-#: templates/boards/staticpages/help.html:12
msgid "Italic text"
msgstr "Курсивный текст"
-#: templates/boards/staticpages/help.html:13
+#: templates/boards/staticpages/help.html:12
msgid "Bold text"
msgstr "Полужирный текст"
-#: templates/boards/staticpages/help.html:14
+#: templates/boards/staticpages/help.html:13
msgid "Spoiler"
msgstr "Спойлер"
-#: templates/boards/staticpages/help.html:15
+#: templates/boards/staticpages/help.html:14
msgid "Link to a post"
msgstr "Ссылка на сообщение"
-#: templates/boards/staticpages/help.html:16
+#: templates/boards/staticpages/help.html:15
msgid "Strikethrough text"
msgstr "Зачеркнутый текст"
-#: templates/boards/staticpages/help.html:17
-msgid "You need to new line before:"
-msgstr "Перед этими тегами нужна новая строка:"
-
-#: templates/boards/staticpages/help.html:18
+#: templates/boards/staticpages/help.html:16
msgid "Comment"
msgstr "Комментарий"
-
-#: templates/search/search.html:30
-msgid "No results found."
-msgstr "Результаты не найдены."
diff --git a/boards/management/commands/cleanusers.py b/boards/management/commands/cleanusers.py
deleted file mode 100644
--- a/boards/management/commands/cleanusers.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from datetime import datetime, timedelta
-from django.core.management import BaseCommand
-from django.db import transaction
-from django.db.models import Count
-from boards.models import User, Post
-
-__author__ = 'neko259'
-
-OLD_USER_AGE_DAYS = 90
-
-
-class Command(BaseCommand):
- help = 'Removes empty users (that don\'t have posts or tags'
-
- @transaction.atomic
- def handle(self, *args, **options):
- old_registration_date = datetime.now().date() - timedelta(
- OLD_USER_AGE_DAYS)
-
- old_users = User.objects.annotate(tags_count=Count('fav_tags')).filter(
- tags_count=0).filter(registration_time__lt=old_registration_date)
- deleted_users = 0
- for user in old_users:
- if not Post.objects.filter(user=user).exists():
- self.stdout.write('Deleting user %s' % user.user_id)
- user.delete()
- deleted_users += 1
-
- self.stdout.write('Deleted %d users' % deleted_users)
\ No newline at end of file
diff --git a/boards/management/commands/enforce_privacy.py b/boards/management/commands/enforce_privacy.py
--- a/boards/management/commands/enforce_privacy.py
+++ b/boards/management/commands/enforce_privacy.py
@@ -13,4 +13,4 @@ class Command(BaseCommand):
@transaction.atomic
def handle(self, *args, **options):
- Post.objects.all().update(poster_ip=NO_IP, user=None)
\ No newline at end of file
+ Post.objects.all().update(poster_ip=NO_IP)
\ No newline at end of file
diff --git a/boards/mdx_neboard.py b/boards/mdx_neboard.py
--- a/boards/mdx_neboard.py
+++ b/boards/mdx_neboard.py
@@ -1,8 +1,7 @@
# coding=utf-8
-import markdown
-from markdown.inlinepatterns import Pattern
-from markdown.util import etree
+import re
+import bbcode
import boards
@@ -10,13 +9,7 @@ import boards
__author__ = 'neko259'
-AUTOLINK_PATTERN = r'(https?://\S+)'
-QUOTE_PATTERN = r'^(?)(>[^>].*)$'
-REFLINK_PATTERN = r'((>>)(\d+))'
-SPOILER_PATTERN = r'%%([^(%%)]+)%%'
-COMMENT_PATTERN = r'^(//(.+))'
-STRIKETHROUGH_PATTERN = r'~(.+)~'
-DASH_PATTERN = r'--'
+REFLINK_PATTERN = re.compile(r'\d+')
class TextFormatter():
@@ -38,7 +31,7 @@ class TextFormatter():
format_right = ''
-class AutolinkPattern(Pattern):
+class AutolinkPattern():
def handleMatch(self, m):
link_element = etree.Element('a')
href = m.group(2)
@@ -48,44 +41,22 @@ class AutolinkPattern(Pattern):
return link_element
-class QuotePattern(Pattern, TextFormatter):
- name = ''
- preview_left = '> '
+class QuotePattern(TextFormatter):
+ name = 'q'
+ preview_left = ''
preview_right = ''
- format_left = '>'
-
- def handleMatch(self, m):
- quote_element = etree.Element('span')
- quote_element.set('class', 'quote')
- quote_element.text = m.group(2)
-
- return quote_element
+ format_left = '[quote]'
+ format_right = '[/quote]'
-class ReflinkPattern(Pattern):
- def handleMatch(self, m):
- post_id = m.group(4)
-
- posts = boards.models.Post.objects.filter(id=post_id)
- if posts.count() > 0:
- ref_element = etree.Element('a')
-
- post = posts[0]
-
- ref_element.set('href', post.get_url())
- ref_element.text = m.group(2)
-
- return ref_element
-
-
-class SpoilerPattern(Pattern, TextFormatter):
- name = 's'
+class SpoilerPattern(TextFormatter):
+ name = 'spoiler'
preview_left = ''
preview_right = ''
- format_left = '%%'
- format_right = '%%'
+ format_left = '[spoiler]'
+ format_right = '[/spoiler]'
def handleMatch(self, m):
quote_element = etree.Element('span')
@@ -95,35 +66,22 @@ class SpoilerPattern(Pattern, TextFormat
return quote_element
-class CommentPattern(Pattern, TextFormatter):
+class CommentPattern(TextFormatter):
name = ''
preview_left = ''
- format_left = '//'
-
- def handleMatch(self, m):
- quote_element = etree.Element('span')
- quote_element.set('class', 'comment')
- quote_element.text = '//' + m.group(3)
-
- return quote_element
+ format_left = '[comment]'
+ format_right = '[/comment]'
-class StrikeThroughPattern(Pattern, TextFormatter):
+class StrikeThroughPattern(TextFormatter):
name = 's'
preview_left = ''
preview_right = ''
- format_left = '~'
- format_right = '~'
-
- def handleMatch(self, m):
- quote_element = etree.Element('span')
- quote_element.set('class', 'strikethrough')
- quote_element.text = m.group(2)
-
- return quote_element
+ format_left = '[s]'
+ format_right = '[/s]'
class ItalicPattern(TextFormatter):
@@ -131,8 +89,8 @@ class ItalicPattern(TextFormatter):
preview_left = ''
preview_right = ''
- format_left = '_'
- format_right = '_'
+ format_left = '[i]'
+ format_right = '[/i]'
class BoldPattern(TextFormatter):
@@ -140,8 +98,8 @@ class BoldPattern(TextFormatter):
preview_left = ''
preview_right = ''
- format_left = '__'
- format_right = '__'
+ format_left = '[b]'
+ format_right = '[/b]'
class CodePattern(TextFormatter):
@@ -149,52 +107,39 @@ class CodePattern(TextFormatter):
preview_left = ''
preview_right = '
'
- format_left = ' '
-
-
-class DashPattern(Pattern):
- def handleMatch(self, m):
- return u'—'
+ format_left = '[code]'
+ format_right = '[/code]'
-class NeboardMarkdown(markdown.Extension):
- def extendMarkdown(self, md, md_globals):
- self._add_neboard_patterns(md)
- self._delete_patterns(md)
+def render_reflink(tag_name, value, options, parent, context):
+ if not REFLINK_PATTERN.match(value):
+ return u'>>%s' % value
- def _delete_patterns(self, md):
- del md.parser.blockprocessors['quote']
-
- del md.inlinePatterns['image_link']
- del md.inlinePatterns['image_reference']
+ post_id = int(value)
- def _add_neboard_patterns(self, md):
- autolink = AutolinkPattern(AUTOLINK_PATTERN, md)
- quote = QuotePattern(QUOTE_PATTERN, md)
- reflink = ReflinkPattern(REFLINK_PATTERN, md)
- spoiler = SpoilerPattern(SPOILER_PATTERN, md)
- comment = CommentPattern(COMMENT_PATTERN, md)
- strikethrough = StrikeThroughPattern(STRIKETHROUGH_PATTERN, md)
- dash = DashPattern(DASH_PATTERN, md)
+ posts = boards.models.Post.objects.filter(id=post_id)
+ if posts.exists():
+ post = posts[0]
- md.inlinePatterns[u'autolink_ext'] = autolink
- md.inlinePatterns[u'spoiler'] = spoiler
- md.inlinePatterns[u'strikethrough'] = strikethrough
- md.inlinePatterns[u'comment'] = comment
- md.inlinePatterns[u'reflink'] = reflink
- md.inlinePatterns[u'quote'] = quote
- md.inlinePatterns[u'dash'] = dash
+ return u'>>%s' % (post.get_url(), post_id)
+ else:
+ return u'>>%s' % value
-def make_extension(configs=None):
- return NeboardMarkdown(configs=configs)
-
-neboard_extension = make_extension()
-
-
-def markdown_extended(markup):
- return markdown.markdown(markup, [neboard_extension, 'nl2br'],
- safe_mode='escape')
+def bbcode_extended(markup):
+ parser = bbcode.Parser(newline='
')
+ parser.add_formatter('post', render_reflink, strip=True)
+ parser.add_simple_formatter('quote',
+ u'%(value)s')
+ parser.add_simple_formatter('comment',
+ u'')
+ parser.add_simple_formatter('spoiler',
+ u'%(value)s')
+ parser.add_simple_formatter('s',
+ u'%(value)s')
+ parser.add_simple_formatter('code',
+ u'
%(value)s
')
+ return '%s
' % parser.format(markup)
formatters = [
QuotePattern,
diff --git a/boards/migrations/0029_auto__del_setting__del_user__del_field_post_user.py b/boards/migrations/0029_auto__del_setting__del_user__del_field_post_user.py
new file mode 100644
--- /dev/null
+++ b/boards/migrations/0029_auto__del_setting__del_user__del_field_post_user.py
@@ -0,0 +1,146 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Deleting model 'Setting'
+ db.delete_table(u'boards_setting')
+
+ # Deleting model 'User'
+ db.delete_table(u'boards_user')
+
+ # Removing M2M table for field hidden_threads on 'User'
+ db.delete_table(db.shorten_name(u'boards_user_hidden_threads'))
+
+ # Removing M2M table for field fav_threads on 'User'
+ db.delete_table(db.shorten_name(u'boards_user_fav_threads'))
+
+ # Removing M2M table for field fav_tags on 'User'
+ db.delete_table(db.shorten_name(u'boards_user_fav_tags'))
+
+ # Removing M2M table for field hidden_tags on 'User'
+ db.delete_table(db.shorten_name(u'boards_user_hidden_tags'))
+
+ # Deleting field 'Post.user'
+ db.delete_column(u'boards_post', 'user_id')
+
+
+ def backwards(self, orm):
+ # Adding model 'Setting'
+ db.create_table(u'boards_setting', (
+ ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['boards.User'])),
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('value', self.gf('django.db.models.fields.CharField')(max_length=50)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=50)),
+ ))
+ db.send_create_signal('boards', ['Setting'])
+
+ # Adding model 'User'
+ db.create_table(u'boards_user', (
+ ('registration_time', self.gf('django.db.models.fields.DateTimeField')()),
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('user_id', self.gf('django.db.models.fields.CharField')(max_length=50)),
+ ('rank', self.gf('django.db.models.fields.IntegerField')()),
+ ))
+ db.send_create_signal('boards', ['User'])
+
+ # Adding M2M table for field hidden_threads on 'User'
+ m2m_table_name = db.shorten_name(u'boards_user_hidden_threads')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('user', models.ForeignKey(orm['boards.user'], null=False)),
+ ('post', models.ForeignKey(orm['boards.post'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['user_id', 'post_id'])
+
+ # Adding M2M table for field fav_threads on 'User'
+ m2m_table_name = db.shorten_name(u'boards_user_fav_threads')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('user', models.ForeignKey(orm['boards.user'], null=False)),
+ ('post', models.ForeignKey(orm['boards.post'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['user_id', 'post_id'])
+
+ # Adding M2M table for field fav_tags on 'User'
+ m2m_table_name = db.shorten_name(u'boards_user_fav_tags')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('user', models.ForeignKey(orm['boards.user'], null=False)),
+ ('tag', models.ForeignKey(orm['boards.tag'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['user_id', 'tag_id'])
+
+ # Adding M2M table for field hidden_tags on 'User'
+ m2m_table_name = db.shorten_name(u'boards_user_hidden_tags')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('user', models.ForeignKey(orm['boards.user'], null=False)),
+ ('tag', models.ForeignKey(orm['boards.tag'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['user_id', 'tag_id'])
+
+ # Adding field 'Post.user'
+ db.add_column(u'boards_post', 'user',
+ self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['boards.User'], null=True),
+ keep_default=False)
+
+
+ models = {
+ 'boards.ban': {
+ 'Meta': {'object_name': 'Ban'},
+ 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
+ 'reason': ('django.db.models.fields.CharField', [], {'default': "'Auto'", 'max_length': '200'})
+ },
+ 'boards.post': {
+ 'Meta': {'ordering': "('id',)", 'object_name': 'Post'},
+ '_text_rendered': ('django.db.models.fields.TextField', [], {}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'images': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'ip+'", 'to': "orm['boards.PostImage']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True', 'db_index': 'True'}),
+ 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
+ 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
+ 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
+ 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
+ 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'rfp+'", 'to': "orm['boards.Post']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True', 'db_index': 'True'}),
+ 'refmap': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
+ 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'markdown'", 'max_length': '30'}),
+ 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '200'})
+ },
+ 'boards.postimage': {
+ 'Meta': {'ordering': "('id',)", 'object_name': 'PostImage'},
+ 'hash': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
+ 'height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
+ 'pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'pre_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'width': ('django.db.models.fields.IntegerField', [], {'default': '0'})
+ },
+ 'boards.tag': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Tag'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'linked': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}),
+ 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tag+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Thread']"})
+ },
+ 'boards.thread': {
+ 'Meta': {'object_name': 'Thread'},
+ 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'bump_time': ('django.db.models.fields.DateTimeField', [], {}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
+ 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tre+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Tag']", 'symmetrical': 'False'})
+ }
+ }
+
+ complete_apps = ['boards']
\ No newline at end of file
diff --git a/boards/migrations/0030_auto__del_field_tag_linked.py b/boards/migrations/0030_auto__del_field_tag_linked.py
new file mode 100644
--- /dev/null
+++ b/boards/migrations/0030_auto__del_field_tag_linked.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Deleting field 'Tag.linked'
+ db.delete_column(u'boards_tag', 'linked_id')
+
+
+ def backwards(self, orm):
+ # Adding field 'Tag.linked'
+ db.add_column(u'boards_tag', 'linked',
+ self.gf('django.db.models.fields.related.ForeignKey')(to=orm['boards.Tag'], null=True, blank=True),
+ keep_default=False)
+
+
+ models = {
+ 'boards.ban': {
+ 'Meta': {'object_name': 'Ban'},
+ 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
+ 'reason': ('django.db.models.fields.CharField', [], {'default': "'Auto'", 'max_length': '200'})
+ },
+ 'boards.post': {
+ 'Meta': {'ordering': "('id',)", 'object_name': 'Post'},
+ '_text_rendered': ('django.db.models.fields.TextField', [], {}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'images': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'ip+'", 'to': "orm['boards.PostImage']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True', 'db_index': 'True'}),
+ 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
+ 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
+ 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
+ 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
+ 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'rfp+'", 'to': "orm['boards.Post']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True', 'db_index': 'True'}),
+ 'refmap': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
+ 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'bbcode'", 'max_length': '30'}),
+ 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '200'})
+ },
+ 'boards.postimage': {
+ 'Meta': {'ordering': "('id',)", 'object_name': 'PostImage'},
+ 'hash': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
+ 'height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
+ 'pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'pre_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'width': ('django.db.models.fields.IntegerField', [], {'default': '0'})
+ },
+ 'boards.tag': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Tag'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}),
+ 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tag+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Thread']"})
+ },
+ 'boards.thread': {
+ 'Meta': {'object_name': 'Thread'},
+ 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'bump_time': ('django.db.models.fields.DateTimeField', [], {}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
+ 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tre+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Tag']", 'symmetrical': 'False'})
+ }
+ }
+
+ complete_apps = ['boards']
\ No newline at end of file
diff --git a/boards/models/__init__.py b/boards/models/__init__.py
--- a/boards/models/__init__.py
+++ b/boards/models/__init__.py
@@ -5,5 +5,3 @@ from boards.models.thread import Thread
from boards.models.post import Post
from boards.models.tag import Tag
from boards.models.user import Ban
-from boards.models.user import Setting
-from boards.models.user import User
diff --git a/boards/models/post.py b/boards/models/post.py
--- a/boards/models/post.py
+++ b/boards/models/post.py
@@ -20,7 +20,7 @@ APP_LABEL_BOARDS = 'boards'
CACHE_KEY_PPD = 'ppd'
CACHE_KEY_POST_URL = 'post_url'
-POSTS_PER_DAY_RANGE = range(7)
+POSTS_PER_DAY_RANGE = 7
BAN_REASON_AUTO = 'Auto'
@@ -28,7 +28,7 @@ IMAGE_THUMB_SIZE = (200, 150)
TITLE_MAX_LENGTH = 200
-DEFAULT_MARKUP_TYPE = 'markdown'
+DEFAULT_MARKUP_TYPE = 'bbcode'
# TODO This should be removed
NO_IP = '0.0.0.0'
@@ -36,17 +36,14 @@ NO_IP = '0.0.0.0'
# TODO Real user agent should be saved instead of this
UNKNOWN_UA = ''
-SETTING_MODERATE = "moderate"
-
-REGEX_REPLY = re.compile('>>(\d+)')
+REGEX_REPLY = re.compile(r'>>(\d+)')
logger = logging.getLogger(__name__)
class PostManager(models.Manager):
-
- def create_post(self, title, text, image=None, thread=None,
- ip=NO_IP, tags=None, user=None):
+ def create_post(self, title, text, image=None, thread=None, ip=NO_IP,
+ tags=None):
"""
Creates new post
"""
@@ -69,8 +66,7 @@ class PostManager(models.Manager):
poster_ip=ip,
poster_user_agent=UNKNOWN_UA, # TODO Get UA at
# last!
- last_edit_time=posting_time,
- user=user)
+ last_edit_time=posting_time)
if image:
post_image = PostImage.objects.create(image=image)
@@ -80,20 +76,14 @@ class PostManager(models.Manager):
thread.replies.add(post)
if tags:
- linked_tags = []
- for tag in tags:
- tag_linked_tags = tag.get_linked_tags()
- if len(tag_linked_tags) > 0:
- linked_tags.extend(tag_linked_tags)
-
- tags.extend(linked_tags)
map(thread.add_tag, tags)
if new_thread:
Thread.objects.process_oldest_threads()
self.connect_replies(post)
- logger.info('Created post #%d' % post.id)
+ logger.info('Created post #%d with title %s' % (post.id,
+ post.get_title()))
return post
@@ -114,7 +104,7 @@ class PostManager(models.Manager):
post.delete()
- logger.info('Deleted post #%d' % post_id)
+ logger.info('Deleted post #%d (%s)' % (post_id, post.get_title()))
def delete_posts_by_ip(self, ip):
"""
@@ -129,7 +119,7 @@ class PostManager(models.Manager):
Connects replies to a post to show them as a reflink map
"""
- for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
+ for reply_number in re.finditer(REGEX_REPLY, post.text.rendered):
post_id = reply_number.group(1)
ref_post = self.filter(id=post_id)
if ref_post.count() > 0:
@@ -148,28 +138,26 @@ class PostManager(models.Manager):
Gets average count of posts per day for the last 7 days
"""
- today = date.today()
- ppd = cache.get(CACHE_KEY_PPD + str(today))
+ day_end = date.today()
+ day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
+
+ cache_key = CACHE_KEY_PPD + str(day_end)
+ ppd = cache.get(cache_key)
if ppd:
return ppd
- posts_per_days = []
- for i in POSTS_PER_DAY_RANGE:
- day_end = today - timedelta(i + 1)
- day_start = today - timedelta(i + 2)
+ day_time_start = timezone.make_aware(datetime.combine(
+ day_start, dtime()), timezone.get_current_timezone())
+ day_time_end = timezone.make_aware(datetime.combine(
+ day_end, dtime()), timezone.get_current_timezone())
- day_time_start = timezone.make_aware(datetime.combine(
- day_start, dtime()), timezone.get_current_timezone())
- day_time_end = timezone.make_aware(datetime.combine(
- day_end, dtime()), timezone.get_current_timezone())
+ posts_per_period = float(self.filter(
+ pub_time__lte=day_time_end,
+ pub_time__gte=day_time_start).count())
- posts_per_days.append(float(self.filter(
- pub_time__lte=day_time_end,
- pub_time__gte=day_time_start).count()))
+ ppd = posts_per_period / POSTS_PER_DAY_RANGE
- ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
- len(posts_per_days))
- cache.set(CACHE_KEY_PPD + str(today), ppd)
+ cache.set(cache_key, ppd)
return ppd
@@ -196,7 +184,6 @@ class Post(models.Model, Viewable):
thread_new = models.ForeignKey('Thread', null=True, default=None,
db_index=True)
last_edit_time = models.DateTimeField()
- user = models.ForeignKey('User', null=True, default=None, db_index=True)
referenced_posts = models.ManyToManyField('Post', symmetrical=False,
null=True,
@@ -220,13 +207,18 @@ class Post(models.Model, Viewable):
return title
def build_refmap(self):
+ """
+ Builds a replies map string from replies list. This is a cache to stop
+ the server from recalculating the map on every post show.
+ """
map_string = ''
first = True
for refpost in self.referenced_posts.all():
if not first:
map_string += ', '
- map_string += '>>%s' % (refpost.get_url(), refpost.id)
+ map_string += '>>%s' % (refpost.get_url(),
+ refpost.id)
first = False
self.refmap = map_string
@@ -251,10 +243,10 @@ class Post(models.Model, Viewable):
thread = self.get_thread()
thread.add_tag(tag)
self.last_edit_time = edit_time
- self.save()
+ self.save(update_fields=['last_edit_time'])
thread.last_edit_time = edit_time
- thread.save()
+ thread.save(update_fields=['last_edit_time'])
@transaction.atomic
def remove_tag(self, tag):
@@ -263,10 +255,10 @@ class Post(models.Model, Viewable):
thread = self.get_thread()
thread.remove_tag(tag)
self.last_edit_time = edit_time
- self.save()
+ self.save(update_fields=['last_edit_time'])
thread.last_edit_time = edit_time
- thread.save()
+ thread.save(update_fields=['last_edit_time'])
def get_url(self, thread=None):
"""
@@ -343,9 +335,9 @@ class Post(models.Model, Viewable):
def delete(self, using=None):
"""
- Delete all post images and the post itself.
+ Deletes all post images and the post itself.
"""
self.images.all().delete()
- super(Post, self).delete(using)
\ No newline at end of file
+ super(Post, self).delete(using)
diff --git a/boards/models/tag.py b/boards/models/tag.py
--- a/boards/models/tag.py
+++ b/boards/models/tag.py
@@ -1,10 +1,12 @@
from django.template.loader import render_to_string
-from boards.models import Thread, Post
from django.db import models
from django.db.models import Count, Sum
from django.core.urlresolvers import reverse
+
+from boards.models import Thread
from boards.models.base import Viewable
+
__author__ = 'neko259'
@@ -36,7 +38,6 @@ class Tag(models.Model, Viewable):
name = models.CharField(max_length=100, db_index=True)
threads = models.ManyToManyField(Thread, null=True,
blank=True, related_name='tag+')
- linked = models.ForeignKey('Tag', null=True, blank=True)
def __unicode__(self):
return self.name
@@ -51,29 +52,6 @@ class Tag(models.Model, Viewable):
def get_thread_count(self):
return self.threads.count()
- def get_linked_tags(self):
- """
- Gets tags linked to the current one.
- """
-
- tag_list = []
- self.get_linked_tags_list(tag_list)
-
- return tag_list
-
- def get_linked_tags_list(self, tag_list=[]):
- """
- Returns the list of tags linked to current. The list can be got
- through returned value or tag_list parameter
- """
-
- linked_tag = self.linked
-
- if linked_tag and not (linked_tag in tag_list):
- tag_list.append(linked_tag)
-
- linked_tag.get_linked_tags_list(tag_list)
-
def get_post_count(self, archived=False):
"""
Gets posts count for the tag's threads.
diff --git a/boards/models/user.py b/boards/models/user.py
--- a/boards/models/user.py
+++ b/boards/models/user.py
@@ -1,122 +1,10 @@
from django.db import models
-from django.db.models import Count
-from boards import settings
-from boards.models import Post
-from django.core.cache import cache
__author__ = 'neko259'
-RANK_ADMIN = 0
-RANK_MODERATOR = 10
-RANK_USER = 100
-
BAN_REASON_AUTO = 'Auto'
BAN_REASON_MAX_LENGTH = 200
-VETERAN_POSTS = 1000
-
-
-class User(models.Model):
-
- class Meta:
- app_label = 'boards'
-
- user_id = models.CharField(max_length=50)
- rank = models.IntegerField()
-
- registration_time = models.DateTimeField()
-
- fav_tags = models.ManyToManyField('Tag', null=True, blank=True)
- fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
- blank=True)
-
- hidden_tags = models.ManyToManyField('Tag', null=True, blank=True,
- related_name='ht+')
- hidden_threads = models.ManyToManyField('Post', null=True, blank=True,
- related_name='hth+')
-
- def save_setting(self, name, value):
- setting, created = Setting.objects.get_or_create(name=name, user=self)
- setting.value = str(value)
- setting.save()
-
- return setting
-
- def get_setting(self, name):
- if Setting.objects.filter(name=name, user=self).exists():
- setting = Setting.objects.get(name=name, user=self)
- setting_value = setting.value
- else:
- setting_value = None
-
- return setting_value
-
- def is_moderator(self):
- return RANK_MODERATOR >= self.rank
-
- def get_sorted_fav_tags(self):
- cache_key = self._get_tag_cache_key()
- fav_tags = cache.get(cache_key)
- if fav_tags:
- return fav_tags
-
- tags = self.fav_tags.annotate(Count('threads')) \
- .filter(threads__count__gt=0).order_by('name')
-
- if tags:
- cache.set(cache_key, tags)
-
- return tags
-
- def get_post_count(self):
- return Post.objects.filter(user=self).count()
-
- def __unicode__(self):
- return self.user_id + '(' + str(self.rank) + ')'
-
- def get_last_access_time(self):
- """
- Gets user's last post time.
- """
-
- posts = Post.objects.filter(user=self)
- if posts.exists() > 0:
- return posts.latest('pub_time').pub_time
-
- def add_tag(self, tag):
- self.fav_tags.add(tag)
- cache.delete(self._get_tag_cache_key())
-
- def remove_tag(self, tag):
- self.fav_tags.remove(tag)
- cache.delete(self._get_tag_cache_key())
-
- def hide_tag(self, tag):
- self.hidden_tags.add(tag)
-
- def unhide_tag(self, tag):
- self.hidden_tags.remove(tag)
-
- def is_veteran(self):
- """
- Returns if a user is old (veteran).
- """
-
- return self.get_post_count() >= VETERAN_POSTS
-
- def _get_tag_cache_key(self):
- return self.user_id + '_tags'
-
-
-class Setting(models.Model):
-
- class Meta:
- app_label = 'boards'
-
- name = models.CharField(max_length=50)
- value = models.CharField(max_length=50)
- user = models.ForeignKey(User)
-
class Ban(models.Model):
diff --git a/boards/settings.py b/boards/settings.py
--- a/boards/settings.py
+++ b/boards/settings.py
@@ -17,4 +17,7 @@ LAST_REPLIES_COUNT = 3
# Enable archiving threads instead of deletion when the thread limit is reached
ARCHIVE_THREADS = True
# Limit posting speed
-LIMIT_POSTING_SPEED = False
\ No newline at end of file
+LIMIT_POSTING_SPEED = False
+
+# This password is used to add admin permissions to the user
+MASTER_PASSWORD = u'password'
\ No newline at end of file
diff --git a/boards/static/css/md/base_page.css b/boards/static/css/md/base_page.css
--- a/boards/static/css/md/base_page.css
+++ b/boards/static/css/md/base_page.css
@@ -217,6 +217,16 @@ blockquote {
font-style: italic;
}
+.multiquote {
+ border-left: solid 4px #ccc;
+ padding: 3px;
+ display: inline-block;
+ background: #222;
+ border-right: solid 1px #ccc;
+ border-top: solid 1px #ccc;
+ border-bottom: solid 1px #ccc;
+}
+
.spoiler {
background: white;
color: white;
diff --git a/boards/static/js/thread.js b/boards/static/js/thread.js
--- a/boards/static/js/thread.js
+++ b/boards/static/js/thread.js
@@ -35,10 +35,10 @@ function moveCaretToEnd(el) {
}
function addQuickReply(postId) {
- var textToAdd = '>>' + postId + '\n\n';
+ var textToAdd = '[post]' + postId + '[/post]\n';
var selection = window.getSelection().toString();
if (selection.length > 0) {
- textToAdd += '> ' + selection + '\n\n';
+ textToAdd += '[quote]' + selection + '[/quote]\n';
}
var textAreaId = 'textarea';
diff --git a/boards/templates/boards/base.html b/boards/templates/boards/base.html
--- a/boards/templates/boards/base.html
+++ b/boards/templates/boards/base.html
@@ -46,7 +46,11 @@
{% block metapanel %}{% endblock %}
- [
{% trans 'Login' %}]
+ {% if moderator %}
+ [
{% trans 'Logout' %}]
+ {% else %}
+ [
{% trans 'Login' %}]
+ {% endif %}
[
{% trans 'Search' %}]
{% with ppd=posts_per_day|floatformat:2 %}
{% blocktrans %}Speed: {{ ppd }} posts per day{% endblocktrans %}
diff --git a/boards/templates/boards/post.html b/boards/templates/boards/post.html
--- a/boards/templates/boards/post.html
+++ b/boards/templates/boards/post.html
@@ -83,7 +83,7 @@
{% cache 600 post_thread thread.id thread.last_edit_time LANGUAGE_CODE need_open_link %}