initial
This commit is contained in:
commit
2c2812c47c
279 changed files with 17212 additions and 0 deletions
0
__init__.py
Normal file
0
__init__.py
Normal file
0
apps/__init__.py
Normal file
0
apps/__init__.py
Normal file
0
apps/account/__init__.py
Normal file
0
apps/account/__init__.py
Normal file
65
apps/account/auth_key.py
Normal file
65
apps/account/auth_key.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
import os
|
||||
from datetime import datetime
|
||||
import hashlib
|
||||
import time
|
||||
from base64 import b64encode, b64decode
|
||||
from urllib import urlencode
|
||||
from cgi import parse_qs
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
def strftime(when):
|
||||
return when.strftime('%d-%m-%y-%H-%M-%S')
|
||||
|
||||
|
||||
def strptime(when):
|
||||
try:
|
||||
return datetime.fromtimestamp(time.mktime(
|
||||
time.strptime(when, '%d-%m-%y-%H-%M-%S')))
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def generate_key(**kwargs):
|
||||
"""
|
||||
Return the hash of kwargs and the
|
||||
"one string" representation of hash and kwargs
|
||||
"""
|
||||
|
||||
pairs = []
|
||||
for key, value in kwargs.iteritems():
|
||||
if isinstance(value, unicode):
|
||||
value = value.encode('utf-8')
|
||||
if isinstance(value, datetime):
|
||||
value = strftime(value)
|
||||
pairs.append((key, value))
|
||||
hash = hashlib.sha1(settings.SECRET_KEY + urlencode(pairs)).hexdigest()
|
||||
pairs.append(('_hash', hash))
|
||||
return hash, b64encode(urlencode(pairs))
|
||||
|
||||
|
||||
def wrap_url(url, **kwargs):
|
||||
"""
|
||||
Create new authorization key and append it to the url.
|
||||
"""
|
||||
|
||||
hash, b64 = generate_key(**kwargs)
|
||||
clue = '?' in url and '&' or '?'
|
||||
url = '%s%sauthkey=%s' % (url, clue, b64)
|
||||
return url
|
||||
|
||||
|
||||
def decode_key(b64):
|
||||
return dict((x[0], x[1][0]) for x in parse_qs(b64decode(b64)).iteritems())
|
||||
|
||||
|
||||
def validate_key(b64):
|
||||
if b64:
|
||||
kwargs = decode_key(b64)
|
||||
kwargs.pop('_hash')
|
||||
expired = strptime(kwargs.get('expired'))
|
||||
if expired:
|
||||
real_key, real_b64 = generate_key(**kwargs)
|
||||
if real_b64 == b64 and datetime.now() < expired:
|
||||
return True
|
||||
return False
|
222
apps/account/forms.py
Normal file
222
apps/account/forms.py
Normal file
|
@ -0,0 +1,222 @@
|
|||
# -*- coding: utf-8
|
||||
|
||||
import re
|
||||
|
||||
from django import forms
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.template import loader
|
||||
|
||||
from apps.forum.models import Profile
|
||||
from apps.forum.models import TZ_CHOICES, PRIVACY_CHOICES
|
||||
|
||||
ACCOUNT_CAPTCHA = getattr(settings, 'ACCOUNT_CAPTCHA', False)
|
||||
|
||||
if ACCOUNT_CAPTCHA:
|
||||
from apps.captcha.fields import CaptchaField
|
||||
|
||||
RE_USERNAME = getattr(settings, 'ACCOUNT_RE_USERNAME',
|
||||
re.compile(r'[a-z0-9][_a-z0-9]*[a-z0-9]$', re.I))
|
||||
USERNAME_MIN_LENGTH = getattr(settings, 'ACCOUNT_USERNAME_MIN_LENGTH', 3)
|
||||
USERNAME_MAX_LENGTH = getattr(settings, 'ACCOUNT_USERNAME_MAX_LENGTH', 20)
|
||||
|
||||
PASSWORD_MIN_LENGTH = getattr(settings, 'ACCOUNT_PASSWORD_MIN_LENGTH', 5)
|
||||
PASSWORD_MAX_LENGTH = getattr(settings, 'ACCOUNT_PASSWORD_MAX_LENGTH', 15)
|
||||
|
||||
class UsernameField(forms.CharField):
|
||||
"""
|
||||
Form field for username handling.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(UsernameField, self).__init__(*args, **kwargs)
|
||||
self.label = _(u'Username')
|
||||
self.help_text = _(u'You can use a-z, 0-9 and underscore. Login length should be between %d and %d' % (USERNAME_MIN_LENGTH, USERNAME_MAX_LENGTH))
|
||||
|
||||
|
||||
def clean(self, value):
|
||||
super(UsernameField, self).clean(value)
|
||||
|
||||
if len(value) < USERNAME_MIN_LENGTH:
|
||||
raise forms.ValidationError(_(u'Login length is less than %d' % USERNAME_MIN_LENGTH))
|
||||
if len(value) > USERNAME_MAX_LENGTH:
|
||||
raise forms.ValidationError(_(u'Login length is more than %d' % USERNAME_MAX_LENGTH))
|
||||
if not RE_USERNAME.match(value):
|
||||
raise forms.ValidationError(_(u'Login contains restricted symbols'))
|
||||
|
||||
try:
|
||||
User.objects.get(username__exact=value)
|
||||
except User.DoesNotExist:
|
||||
return value
|
||||
else:
|
||||
raise forms.ValidationError(_(u'This login already registered'))
|
||||
|
||||
|
||||
class PasswordField(forms.CharField):
|
||||
"""
|
||||
Form field for password handling.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PasswordField, self).__init__(*args, **kwargs)
|
||||
self.widget = forms.PasswordInput()
|
||||
self.help_text = ''
|
||||
|
||||
|
||||
def clean(self, value):
|
||||
super(PasswordField, self).clean(value)
|
||||
if len(value) < PASSWORD_MIN_LENGTH:
|
||||
raise forms.ValidationError(_(u'Password length is less than %d' % PASSWORD_MIN_LENGTH))
|
||||
if len(value) > PASSWORD_MAX_LENGTH:
|
||||
raise forms.ValidationError(_(u'Password length is more than %d' % PASSWORD_MAX_LENGTH))
|
||||
return value
|
||||
|
||||
|
||||
class RegistrationForm(forms.Form):
|
||||
username = UsernameField()
|
||||
email = forms.EmailField(label=_('Email'))
|
||||
password = PasswordField(label=_('Password'))
|
||||
password_dup = PasswordField(label=_('Confirm password'))
|
||||
time_zone = forms.ChoiceField(label=_('Time zone'), choices=TZ_CHOICES)
|
||||
privacy_permission = forms.ChoiceField(label=_('Privacy permission'), choices=PRIVACY_CHOICES)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.base_fields['privacy_permission'].widget = forms.RadioSelect(
|
||||
choices=self.base_fields['privacy_permission'].choices
|
||||
)
|
||||
super(RegistrationForm, self).__init__(*args, **kwargs)
|
||||
if ACCOUNT_CAPTCHA:
|
||||
self.fields['captcha'] = CaptchaField()
|
||||
|
||||
|
||||
def clean_email(self):
|
||||
#return self.cleaned_data.get('email','')
|
||||
try:
|
||||
User.objects.get(email__exact=self.cleaned_data['email'])
|
||||
except User.DoesNotExist:
|
||||
return self.cleaned_data['email']
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
raise forms.ValidationError(_(u'This email already registered'))
|
||||
|
||||
|
||||
def clean(self):
|
||||
pwd1 = self.cleaned_data.get('password')
|
||||
pwd2 = self.cleaned_data.get('password_dup')
|
||||
if pwd1 and pwd2:
|
||||
if pwd1 != pwd2:
|
||||
# show error on top of password_dup field
|
||||
self._errors['password_dup'] = [_('Passwords do not match')]
|
||||
return self.cleaned_data
|
||||
|
||||
|
||||
def save(self):
|
||||
username = self.cleaned_data['username']
|
||||
email = self.cleaned_data['email']
|
||||
password = self.cleaned_data['password']
|
||||
time_zone = self.cleaned_data['time_zone']
|
||||
privacy_permission = self.cleaned_data['privacy_permission']
|
||||
user = User.objects.create_user(username, email, password=password)
|
||||
user.save()
|
||||
profile = Profile(user = user,
|
||||
time_zone = time_zone,
|
||||
privacy_permission = privacy_permission,
|
||||
status = 'Member'
|
||||
)
|
||||
profile.save()
|
||||
return user
|
||||
|
||||
|
||||
class RestorePasswordForm(forms.Form):
|
||||
email = forms.EmailField(label=_('Email'))
|
||||
|
||||
def clean_email(self):
|
||||
if 'email' in self.cleaned_data:
|
||||
email = self.cleaned_data['email']
|
||||
if User.objects.filter(email=email).count():
|
||||
return email
|
||||
else:
|
||||
raise forms.ValidationError(_(u'This email is not registered'))
|
||||
|
||||
|
||||
class LoginForm(forms.Form):
|
||||
username = forms.CharField(label=_('Username'))
|
||||
password = forms.CharField(label=_('Password'), widget=forms.PasswordInput)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.request = kwargs.pop('request')
|
||||
self.base_fields['username'].help_text = ''
|
||||
#self.base_fields['password'].widget = forms.PasswordInput()
|
||||
self.base_fields['password'].help_text = ''
|
||||
super(LoginForm, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
def clean(self):
|
||||
super(LoginForm, self).clean()
|
||||
if self.is_valid():
|
||||
user = authenticate(
|
||||
username=self.cleaned_data['username'],
|
||||
password=self.cleaned_data['password'])
|
||||
if not user is None:
|
||||
if user.is_active:
|
||||
login(self.request, user)
|
||||
return self.cleaned_data
|
||||
else:
|
||||
raise forms.ValidationError(_(u'Sorry. You account is not active. Maybe you didn\'t activate it.'))
|
||||
else:
|
||||
raise forms.ValidationError(_(u'Incorrect login or password'))
|
||||
|
||||
|
||||
class NewPasswordForm(forms.Form):
|
||||
"""
|
||||
Form for changing user's password.
|
||||
"""
|
||||
|
||||
old_password = PasswordField(label=_(u'Old password'))
|
||||
password = PasswordField(label=_(u'Password'))
|
||||
password_confirmation = PasswordField(label=_(u'Password (confirmation)'))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop('user')
|
||||
if not self.user.has_usable_password():
|
||||
self.base_fields['old_password'] = forms.Field(widget=forms.HiddenInput, required=False)
|
||||
super(NewPasswordForm, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
def clean_old_password(self):
|
||||
password = self.cleaned_data['old_password']
|
||||
if password:
|
||||
test_user = authenticate(username=self.user.username,
|
||||
password=password)
|
||||
if test_user:
|
||||
return password
|
||||
else:
|
||||
raise forms.ValidationError(_('Incorrect old password'))
|
||||
|
||||
|
||||
def clean_password_confirmation(self):
|
||||
pass1 = self.cleaned_data['password']
|
||||
pass2 = self.cleaned_data['password_confirmation']
|
||||
if pass1 != pass2:
|
||||
raise forms.ValidationError(_(u'The passwords do not match'))
|
||||
else:
|
||||
return pass1
|
||||
|
||||
|
||||
def save(self):
|
||||
self.user.set_password(self.cleaned_data['password'])
|
||||
self.user.save()
|
||||
return self.user
|
||||
|
||||
class NewEmailForm(forms.Form):
|
||||
"""
|
||||
Form for email chanage.
|
||||
"""
|
||||
|
||||
email = forms.EmailField(label=_(u'New email'))
|
||||
|
||||
def save(self):
|
||||
pass
|
0
apps/account/managers.py
Normal file
0
apps/account/managers.py
Normal file
98
apps/account/middleware.py
Normal file
98
apps/account/middleware.py
Normal file
|
@ -0,0 +1,98 @@
|
|||
"""
|
||||
This module contains middlewares for account application:
|
||||
* DebugLoginMiddleware
|
||||
* OneTimeCodeAuthMiddleware
|
||||
* TestCookieMiddleware
|
||||
"""
|
||||
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth import login, authenticate
|
||||
from django.conf import settings #PC
|
||||
|
||||
from apps.account.util import email_template
|
||||
from apps.account.auth_key import validate_key, decode_key, decode_key
|
||||
from apps.account.settings import ACCOUNT_DOMAIN
|
||||
|
||||
def login_user(request, user):
|
||||
user.backend = 'django.contrib.auth.backends.ModelBackend'
|
||||
login(request, user)
|
||||
|
||||
|
||||
class DebugLoginMiddleware(object):
|
||||
"""
|
||||
This middleware authenticates user with just an
|
||||
ID parameter in the URL.
|
||||
This is dangerous middleware, use it with caution.
|
||||
"""
|
||||
|
||||
def process_request(self, request):
|
||||
"""
|
||||
Login user with ID from loginas param of the query GET data.
|
||||
|
||||
Do it only then settings.ACCOUNT_LOGIN_DEBUG is True
|
||||
"""
|
||||
|
||||
if getattr(settings, 'ACCOUNT_LOGIN_DEBUG', False):
|
||||
try:
|
||||
id = int(request.GET.get('loginas', 0))
|
||||
user = User.objects.get(pk=id)
|
||||
except ValueError:
|
||||
return
|
||||
except User.DoesNotExist:
|
||||
return
|
||||
else:
|
||||
login_user(user)
|
||||
|
||||
|
||||
class AuthKeyMiddleware(object):
|
||||
"""
|
||||
This middleware can authenticate user with auth key in HTTP request.
|
||||
"""
|
||||
|
||||
def process_request(self, request):
|
||||
|
||||
key = request.REQUEST.get('authkey', None)
|
||||
|
||||
if validate_key(key):
|
||||
args = decode_key(key)
|
||||
|
||||
try:
|
||||
print args
|
||||
user = User.objects.get(username=args['username'])
|
||||
except User.DoesNotExist:
|
||||
return
|
||||
|
||||
action = args.get('action')
|
||||
if 'activation' == action:
|
||||
if not user.is_active:
|
||||
user.is_active = True
|
||||
user.save()
|
||||
email_template(user.email, 'account/mail/welcome.txt',
|
||||
**{'login': user.username, 'domain': ACCOUNT_DOMAIN})
|
||||
if user.is_active:
|
||||
if 'new_password' == action:
|
||||
if args.get('password'):
|
||||
user.set_password(args['password'])
|
||||
user.save()
|
||||
|
||||
if 'new_email' == action:
|
||||
if args.get('email'):
|
||||
user.email = args['email']
|
||||
user.save()
|
||||
|
||||
login_user(request, user)
|
||||
|
||||
|
||||
class TestCookieMiddleware(object):
|
||||
"""
|
||||
This middleware fixes error that appeares when user try to login
|
||||
not from page that was generated with django.contrib.auth.views.login view.
|
||||
"""
|
||||
|
||||
def process_view(self, request, view_func, view_args, view_kwargs):
|
||||
"""
|
||||
Setup test cookie.
|
||||
"""
|
||||
|
||||
request.session.set_test_cookie()
|
0
apps/account/models.py
Normal file
0
apps/account/models.py
Normal file
5
apps/account/settings.py
Normal file
5
apps/account/settings.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django.conf import settings
|
||||
|
||||
|
||||
ACCOUNT_DOMAIN = getattr(settings, 'ACCOUNT_DOMAIN', 'fix.your.settings.com')
|
||||
ACCOUNT_AUTH_KEY_TIMEOUT = getattr(settings, 'ACCOUNT_AUTH_KEY_TIMEOUT', 60 * 60 * 24)
|
29
apps/account/templates/account/login.html
Normal file
29
apps/account/templates/account/login.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
{% extends "forum/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="blockform">
|
||||
<h2><span>{% trans "Login" %}</span></h2>
|
||||
<div class="box">
|
||||
<form class="login-form" method="post">
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Enter your username and password below" %}</legend>
|
||||
|
||||
<div class="infldset">
|
||||
<label class="conl"><strong>{{ form.username.label }}</strong><br />{{ form.username }}<br /></label>
|
||||
<label class="conl"><strong>{{ form.password.label }}</strong><br />{{ form.password }}<br /></label>
|
||||
<p class="clearb">{% trans "If you have not registered or have forgotten your password click on the appropriate link below." %}</p>
|
||||
<p><a href="{% url registration_register %}">{% trans "Not registered yet?" %}</a>
|
||||
|
||||
<a href="{% url auth_password_reset %}">{% trans "Forgotten your password?" %}</a></p>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<p><input type="submit" value="{% trans "Login" %}" /></p>
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
4
apps/account/templates/account/mail/new_email.txt
Normal file
4
apps/account/templates/account/mail/new_email.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
{% load i18n %}{% blocktrans %}"Email change on {{ domain }}{% endblocktrans %}
|
||||
{% blocktrans %}You have requested the change of email on {{ domain }}{% endblocktrans %}.
|
||||
{% blocktrans %}After visiting the {{ url }}, the new email will be set for your accont{% endblocktrans %}.
|
||||
{% blocktrans %}New email: {{ email }}{% endblocktrans %}
|
3
apps/account/templates/account/mail/registration.txt
Normal file
3
apps/account/templates/account/mail/registration.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
{% load i18n %}{% blocktrans %}Registration on {{ domain }}{% endblocktrans %}
|
||||
{% blocktrans %}You have begun registration on {{ domain }}{% endblocktrans %}.
|
||||
{% blocktrans %}If you want activate you account {{ login }}, follow the link please: {{ url }}{% endblocktrans %}
|
4
apps/account/templates/account/mail/restore_password.txt
Normal file
4
apps/account/templates/account/mail/restore_password.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
{% load i18n %}{% blocktrans %}Password restore on {{ domain }}{% endblocktrans %}
|
||||
{% blocktrans %}You have requested password restore on {{ domain }}{% endblocktrans %}.
|
||||
{% blocktrans %}After visiting the {{ url }}, the new password will be set for your account{% endblocktrans %}.
|
||||
{% blocktrans %}New password: {{ password }}{% endblocktrans %}
|
4
apps/account/templates/account/mail/welcome.txt
Normal file
4
apps/account/templates/account/mail/welcome.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
{% load i18n %}{% blocktrans %}Successfuly registration on {{ domain }}{% endblocktrans %}
|
||||
Congratulations!
|
||||
{% blocktrans %}You have registered on {{ domain }}{% endblocktrans %}.
|
||||
{% blocktrans %}Your login is {{ login }}{% endblocktrans %}.
|
7
apps/account/templates/account/message.html
Normal file
7
apps/account/templates/account/message.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Important information" %}</h1>
|
||||
<p>{{ message }}</p>
|
||||
{% endblock %}
|
31
apps/account/templates/account/new_password.html
Normal file
31
apps/account/templates/account/new_password.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
{% extends "forum/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="blockform">
|
||||
<h2><span>{% trans "Change password" %}</span></h2>
|
||||
<div class="box">
|
||||
<form id="change_pass" method="post">
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Enter and confirm your new password" %}</legend>
|
||||
|
||||
<div class="infldset">
|
||||
<label><strong>{{ form.old_password.label }}:</strong><br />
|
||||
{{ form.old_password }}<br /></label>
|
||||
<label class="conl"><strong>{{ form.password.label }}:</strong><br />
|
||||
{{ form.password }}<br /></label>
|
||||
<label class="conl"><strong>{{ form.password_confirmation.label }}:</strong><br />
|
||||
{{ form.password_confirmation }}<br /></label>
|
||||
|
||||
<div class="clearb"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
</div>
|
||||
<p><input type="submit" name="update" value="{% trans "Submit" %}" /><a href="javascript:history.go(-1)">{% trans "Go back" %}</a></p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
88
apps/account/templates/account/registration.html
Normal file
88
apps/account/templates/account/registration.html
Normal file
|
@ -0,0 +1,88 @@
|
|||
{% extends "forum/base.html" %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="blockform">
|
||||
<h2><span>{% trans "Register" %}</span></h2>
|
||||
<div class="box">
|
||||
<form method="post">
|
||||
<div class="inform">
|
||||
<div class="forminfo">
|
||||
<h3>{% trans "Important information" %}</h3>
|
||||
|
||||
<p>{% trans "Registration will grant you access to a number of features and capabilities otherwise unavailable. These functions include the ability to edit and delete posts, design your own signature that accompanies your posts and much more. If you have any questions regarding this forum you should ask an administrator." %}</p>
|
||||
<p>{% trans "elow is a form you must fill out in order to register. Once you are registered you should visit your profile and review the different settings you can change. The fields below only make up a small part of all the settings you can alter in your profile." %}</p>
|
||||
</div>
|
||||
<fieldset>
|
||||
<legend>{% blocktrans %}Please enter a username from {{ username_min_length }} characters long {% endblocktrans %}</legend>
|
||||
<div class="infldset">
|
||||
|
||||
<label><strong>{{ form.username.label }}</strong><br />{{ form.username }}<br /></label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Please enter and confirm your chosen password" %}</legend>
|
||||
<div class="infldset">
|
||||
<label class="conl"><strong>{{ form.password.label }}</strong><br />{{ form.password }}<br /></label>
|
||||
<label class="conl"><strong>{{ form.password_dup.label }}</strong><br />{{ form.password_dup }}<br /></label>
|
||||
<p class="clearb">{% blocktrans %}Passwords can be from {{ password_min_length }} characters long. Passwords are case sensitive.{% endblocktrans %}</p>
|
||||
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Image Verification" %}</legend>
|
||||
<div class="infldset">
|
||||
<label class="conl">{{ form.captcha }}<br /></label>
|
||||
<p class="clearb">{% trans "Please enter 6 letters or digits that appear in the image opposite" %}</p>
|
||||
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Enter a valid e-mail address" %}</legend>
|
||||
<div class="infldset">
|
||||
<label><strong>{{ form.email.label }}</strong><br />
|
||||
|
||||
{{ form.email }}<br /></label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Set your localisation options" %}</legend>
|
||||
<div class="infldset">
|
||||
|
||||
<label>{% trans "Timezone: For the forum to display times correctly you must select your local timezone." %}
|
||||
<br />
|
||||
{{ form.time_zone }}
|
||||
<br /></label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="inform">
|
||||
|
||||
<fieldset>
|
||||
<legend>{% trans "Set your privacy options" %}</legend>
|
||||
<div class="infldset">
|
||||
<p>{% trans "Select whether you want your e-mail address to be viewable to other users or not and if you want other users to be able to send you e-mail via the forum (form e-mail) or not." %}</p>
|
||||
<div class="rbox">
|
||||
{{ form.privacy_permission }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</div>
|
||||
<p><input type="submit" value="{% trans "Register" %}" /></p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
25
apps/account/templates/account/restore_password.html
Normal file
25
apps/account/templates/account/restore_password.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="blockform">
|
||||
<h2><span>{% trans "Request password" %}</span></h2>
|
||||
<div class="box">
|
||||
<form method="post">
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Enter the e-mail address with which you registered" %}</legend>
|
||||
|
||||
<div class="infldset">
|
||||
{{ form.email }}
|
||||
<p>{% trans "A new password together with a link to activate the new password will be sent to that address." %}</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<p><input type="submit" value="{% trans "Submit" %}"/><a href="javascript:history.go(-1)">{% trans "Go back" %}</a></p>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
130
apps/account/tests.py
Normal file
130
apps/account/tests.py
Normal file
|
@ -0,0 +1,130 @@
|
|||
from datetime import datetime, timedelta
|
||||
import pickle
|
||||
import cgi
|
||||
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import User
|
||||
from django.test.client import Client
|
||||
from django.conf.urls.defaults import *
|
||||
from django.http import HttpResponse
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import login, authenticate
|
||||
|
||||
from apps.account.auth_key import strftime, strptime,\
|
||||
generate_key, wrap_url, validate_key
|
||||
|
||||
class AccountTestCase(TestCase):
|
||||
urls = 'account.tests'
|
||||
|
||||
def setUp(self):
|
||||
User.objects.all().delete()
|
||||
u = User(username='user', email='user@host.com')
|
||||
u.set_password('pass')
|
||||
u.is_active = True
|
||||
u.save()
|
||||
self.user = u
|
||||
|
||||
u = User(username='ban_user', email='ban_user@host.com')
|
||||
u.set_password('pass')
|
||||
u.is_active = False
|
||||
u.save()
|
||||
self.ban_user = u
|
||||
|
||||
# New email
|
||||
expired = datetime.now() + timedelta(days=1)
|
||||
url, args = process_url(test_url, username=self.user.username, expired=expired,
|
||||
action='new_email', email='user_gaz@host.com')
|
||||
self.assertEqual('user@host.com', self.user.email)
|
||||
resp = self.client.get(url, args)
|
||||
self.assertEqual(self.client.session['_auth_user_id'], self.user.id)
|
||||
user = User.objects.get(pk=self.user.id)
|
||||
self.assertEqual(user.email, 'user_gaz@host.com')
|
||||
|
||||
|
||||
class AuthCodeTestCase(AccountTestCase):
|
||||
def test_generate_key(self):
|
||||
# should not fail
|
||||
generate_key(username=self.user.username, expired=datetime.now())
|
||||
|
||||
|
||||
def test_wrap_url(self):
|
||||
expired = datetime.now()
|
||||
key, b64 = generate_key(username=self.user.username, expired=expired)
|
||||
|
||||
link = 'http://ya.ru'
|
||||
self.assertEqual(wrap_url(link, username=self.user.username, expired=expired),
|
||||
'%s?authkey=%s' % (link, b64))
|
||||
|
||||
link = 'http://ya.ru?foo=bar'
|
||||
self.assertEqual(wrap_url(link, username=self.user.username, expired=expired),
|
||||
'%s&authkey=%s' % (link, b64))
|
||||
|
||||
|
||||
def test_validate_key(self):
|
||||
expired = datetime.now() - timedelta(seconds=1)
|
||||
key, b64 = generate_key(username=self.user.username, expired=expired)
|
||||
self.assertFalse(validate_key(b64))
|
||||
|
||||
expired = datetime.now() + timedelta(seconds=10)
|
||||
key, b64 = generate_key(username=self.user.username, expired=expired)
|
||||
self.assertTrue(validate_key(b64))
|
||||
|
||||
key, b64 = generate_key(username=self.user.username, expired='asdf')
|
||||
self.assertFalse(validate_key(b64))
|
||||
|
||||
|
||||
class AuthKeyMiddlewareTestCase(AccountTestCase):
|
||||
|
||||
def testActivation(self):
|
||||
def process_url(url, **kwargs):
|
||||
url = wrap_url(url, **kwargs)
|
||||
return url.split('?')[0], cgi.parse_qs(url.split('?')[1])
|
||||
|
||||
test_url = '/account_test_view/'
|
||||
expired = datetime.now() + timedelta(days=1)
|
||||
resp = self.client.get(test_url)
|
||||
|
||||
# Guest has no cookies
|
||||
self.assertFalse(self.client.cookies)
|
||||
|
||||
# Simple authorization
|
||||
url, args = process_url(test_url, username=self.user.username, expired=expired)
|
||||
resp = self.client.get(url, args)
|
||||
self.assertEqual(self.client.session['_auth_user_id'], self.user.id)
|
||||
self.client.session.flush()
|
||||
|
||||
# Baned user can't authorize
|
||||
url, args = process_url(test_url, username=self.ban_user.username, expired=expired)
|
||||
resp = self.client.get(url, args)
|
||||
self.assert_('_auth_user_id' not in self.client.session)
|
||||
self.client.session.flush()
|
||||
|
||||
# Activation of baned user
|
||||
url, args = process_url(test_url, username=self.ban_user.username, expired=expired, action='activation')
|
||||
resp = self.client.get(url, args)
|
||||
self.assertEqual(self.client.session['_auth_user_id'], self.ban_user.id)
|
||||
self.client.session.flush()
|
||||
|
||||
# New password
|
||||
url, args = process_url(test_url, username=self.user.username, expired=expired,
|
||||
action='new_password', password='foobar')
|
||||
self.assertTrue(authenticate(username=self.user.username, password='pass'))
|
||||
resp = self.client.get(url, args)
|
||||
self.assertEqual(self.client.session['_auth_user_id'], self.user.id)
|
||||
self.client.session.flush()
|
||||
self.assertTrue(authenticate(username=self.user.username, password='foobar'))
|
||||
self.client.session.flush()
|
||||
|
||||
# Expired auth key do not work
|
||||
expired = datetime.now() - timedelta(seconds=1)
|
||||
url, args = process_url(url, username=self.user.username, expired=expired)
|
||||
resp = self.client.get(url, args)
|
||||
self.assert_('_auth_user_id' not in self.client.session)
|
||||
|
||||
|
||||
def test_view(request):
|
||||
return HttpResponse(request.user and request.user.username or '')
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url('account_test_view/', test_view, name='account_test_view'),
|
||||
)
|
23
apps/account/urls.py
Normal file
23
apps/account/urls.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from django.conf.urls.defaults import *
|
||||
import django.contrib.auth.views
|
||||
from django.views.generic.simple import direct_to_template
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
from apps.account import views
|
||||
|
||||
# Note that url names are the same as in django-registration application
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^registration/$', views.registration, name='registration_register'),
|
||||
url(r'^login/$', views.login, name='auth_login'),
|
||||
url(r'^logout/$', views.logout, name='auth_logout'),
|
||||
url(r'^reset_password/$', views.restore_password,
|
||||
name='auth_password_reset'),
|
||||
url(r'^change_password/$', views.new_password, name='auth_password_change'),
|
||||
url(r'^created/$', direct_to_template, {'template':'account/created.html'},
|
||||
name='account_created'),
|
||||
url(r'^welcome/$', direct_to_template, {'template':'account/welcome.html'},
|
||||
name='registration_complete'),
|
||||
url(r'^change_email/$', views.change_email, name='auth_email_change'),
|
||||
url(r'^email_changed/$', views.email_changed, name='auth_email_changed'),
|
||||
)
|
100
apps/account/util.py
Normal file
100
apps/account/util.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
# -*- coding: utf-8
|
||||
"""
|
||||
Useful utilities for account application.
|
||||
"""
|
||||
|
||||
import re
|
||||
import os.path
|
||||
from datetime import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.template.loader import get_template
|
||||
from django.template import Context, RequestContext
|
||||
from django.shortcuts import render_to_response
|
||||
from django.core.mail import send_mail
|
||||
from django.conf import settings
|
||||
|
||||
# TODO: this module needs i18n
|
||||
|
||||
def build_redirect_url(request, default_url):
|
||||
"""
|
||||
Retrieve redirect url from session.
|
||||
|
||||
Use default if retrieved one is broken or not safe.
|
||||
"""
|
||||
|
||||
url = request.session.get('login_redirect_url')
|
||||
if not url or '//' in url or ' ' in url:
|
||||
url = default_url
|
||||
try:
|
||||
del request.session['login_redirect_url']
|
||||
except KeyError:
|
||||
pass
|
||||
return url
|
||||
|
||||
|
||||
def parse_template(template_path, **kwargs):
|
||||
"""
|
||||
Load and render template.
|
||||
|
||||
First line of template should contain the subject of email.
|
||||
Return tuple with subject and content.
|
||||
"""
|
||||
|
||||
template = get_template(template_path)
|
||||
context = Context(kwargs)
|
||||
data = template.render(context).strip()
|
||||
subject, content = re.split(r'\r?\n', data, 1)
|
||||
return (subject.strip(), content.strip())
|
||||
|
||||
|
||||
def email_template(rcpt, template_path, **kwargs):
|
||||
"""
|
||||
Load, render and email template.
|
||||
|
||||
**kwargs may contain variables for template rendering.
|
||||
"""
|
||||
|
||||
subject, content = parse_template(template_path, **kwargs)
|
||||
count = send_mail(subject, content, settings.DEFAULT_FROM_EMAIL,
|
||||
[rcpt], fail_silently=True)
|
||||
mail_dir = getattr(settings, 'ACCOUNT_DEBUG_MAIL_DIR', False)
|
||||
|
||||
if mail_dir:
|
||||
# TODO: could rcpt countain symbols restricted for file paths?
|
||||
fname = '%s_%s' % (rcpt, datetime.now().strftime('%H_%M'))
|
||||
fname = os.path.join(mail_dir, fname)
|
||||
# TODO: should we use encoding from conf.settings?
|
||||
data = (u'Subject: %s\n%s' % (subject, content)).encode('utf-8')
|
||||
file(fname, 'w').write(data)
|
||||
|
||||
return bool(count)
|
||||
|
||||
|
||||
def render_to(template_path):
|
||||
"""
|
||||
Decorate the django view.
|
||||
|
||||
Wrap view that return dict of variables, that should be used for
|
||||
rendering the template.
|
||||
Dict returned from view could contain special keys:
|
||||
* MIME_TYPE: mimetype of response
|
||||
* TEMPLATE: template that should be used insted one that was
|
||||
specified in decorator argument
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
def wrapper(request, *args, **kwargs):
|
||||
output = func(request, *args, **kwargs)
|
||||
if not isinstance(output, dict):
|
||||
return output
|
||||
kwargs = {'context_instance': RequestContext(request)}
|
||||
if 'MIME_TYPE' in output:
|
||||
kwargs['mimetype'] = output.pop('MIME_TYPE')
|
||||
|
||||
template = template_path
|
||||
if 'TEMPLATE' in output:
|
||||
template = output.pop('TEMPLATE')
|
||||
return render_to_response(template, output, **kwargs)
|
||||
return wrapper
|
||||
return decorator
|
149
apps/account/views.py
Normal file
149
apps/account/views.py
Normal file
|
@ -0,0 +1,149 @@
|
|||
# -*- coding: utf-8
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib import auth
|
||||
from django.utils.translation import ugettext as _, string_concat
|
||||
from django.http import HttpResponseRedirect
|
||||
|
||||
from apps.account.forms import RegistrationForm, RestorePasswordForm,\
|
||||
NewPasswordForm, LoginForm, NewEmailForm
|
||||
from apps.account.util import email_template, build_redirect_url, render_to
|
||||
from apps.account.auth_key import wrap_url
|
||||
from apps.account.settings import ACCOUNT_DOMAIN, ACCOUNT_AUTH_KEY_TIMEOUT
|
||||
|
||||
def message(msg):
|
||||
"""
|
||||
Shortcut that prepare data for message view.
|
||||
"""
|
||||
|
||||
return {'TEMPLATE': 'account/message.html', 'message': msg}
|
||||
|
||||
|
||||
@render_to('account/registration.html')
|
||||
def registration(request):
|
||||
if request.user.is_authenticated():
|
||||
return message(_('You have to logout before registration'))
|
||||
if not getattr(settings, 'ACCOUNT_REGISTRATION', True):
|
||||
return message(_('Sorry. Registration is disabled.'))
|
||||
|
||||
if 'POST' == request.method:
|
||||
form = RegistrationForm(request.POST)
|
||||
else:
|
||||
form = RegistrationForm()
|
||||
|
||||
if form.is_valid():
|
||||
user = form.save()
|
||||
if getattr(settings, 'ACCOUNT_ACTIVATION', True):
|
||||
user.is_active = False
|
||||
user.save()
|
||||
url = 'http://%s%s' % (ACCOUNT_DOMAIN, reverse('registration_complete'))
|
||||
url = wrap_url(url, username=user.username, action='activation', expired=datetime.now() + timedelta(seconds=ACCOUNT_AUTH_KEY_TIMEOUT))
|
||||
params = {'domain': ACCOUNT_DOMAIN, 'login': user.username, 'url': url}
|
||||
if email_template(user.email, 'account/mail/registration.txt', **params):
|
||||
return HttpResponseRedirect(reverse('account_created'))
|
||||
else:
|
||||
user.delete()
|
||||
return message(_('The error was occuried while sending email with activation code. Account was not created. Please, try later.'))
|
||||
else:
|
||||
user.backend = 'django.contrib.auth.backends.ModelBackend'
|
||||
auth.login(request, user)
|
||||
email_template(user.email, 'account/mail/welcome.txt',
|
||||
**{'domain': ACCOUNT_DOMAIN, 'login': user.username})
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
return {'form': form,
|
||||
'username_min_length': settings.ACCOUNT_USERNAME_MIN_LENGTH,
|
||||
'password_min_length': settings.ACCOUNT_PASSWORD_MIN_LENGTH,
|
||||
}
|
||||
|
||||
|
||||
@render_to('account/message.html')
|
||||
def logout(request):
|
||||
auth.logout(request)
|
||||
redirect_url = build_redirect_url(request, settings.LOGIN_REDIRECT_URL)
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
|
||||
|
||||
@render_to('account/restore_password.html')
|
||||
def restore_password(request):
|
||||
if 'POST' == request.method:
|
||||
form = RestorePasswordForm(request.POST)
|
||||
else:
|
||||
form = RestorePasswordForm()
|
||||
|
||||
if form.is_valid():
|
||||
password = User.objects.make_random_password()
|
||||
user = User.objects.get(email=form.cleaned_data['email'])
|
||||
url = 'http://%s%s' % (ACCOUNT_DOMAIN, reverse('auth_password_change'))
|
||||
url = wrap_url(url, username=user.username, action='new_password',
|
||||
expired=datetime.now() + timedelta(seconds=ACCOUNT_AUTH_KEY_TIMEOUT),
|
||||
password=password)
|
||||
args = {'domain': ACCOUNT_DOMAIN, 'url': url, 'password': password}
|
||||
if email_template(user.email, 'account/mail/restore_password.txt', **args):
|
||||
return message(_('Check the mail please'))
|
||||
else:
|
||||
return message(_('Unfortunately we could not send you email in current time. Please, try later'))
|
||||
return {'form': form}
|
||||
|
||||
|
||||
@render_to('account/login.html')
|
||||
def login(request):
|
||||
if request.user.is_authenticated():
|
||||
return message(_('You are already authenticated'))
|
||||
|
||||
if 'POST' == request.method:
|
||||
form = LoginForm(request.POST, request=request)
|
||||
else:
|
||||
form = LoginForm(request=request)
|
||||
|
||||
request.session['login_redirect_url'] = request.GET.get('next')
|
||||
if form.is_valid():
|
||||
redirect_url = build_redirect_url(request, settings.LOGIN_REDIRECT_URL)
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
return {'form': form}
|
||||
|
||||
|
||||
@login_required
|
||||
@render_to('account/new_password.html')
|
||||
def new_password(request):
|
||||
if 'POST' == request.method:
|
||||
form = NewPasswordForm(request.POST, user=request.user)
|
||||
else:
|
||||
form = NewPasswordForm(user=request.user)
|
||||
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
redirect_url = build_redirect_url(request, settings.LOGIN_REDIRECT_URL)
|
||||
#return message(_('Password was changed'))
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
return {'form': form}
|
||||
|
||||
@login_required
|
||||
@render_to('account/change_email.html')
|
||||
def change_email(request):
|
||||
if 'POST' == request.method:
|
||||
form = NewEmailForm(request.POST)
|
||||
else:
|
||||
form = NewEmailForm()
|
||||
|
||||
if form.is_valid():
|
||||
email = form.cleaned_data['email']
|
||||
url = 'http://%s%s' % (ACCOUNT_DOMAIN, reverse('auth_email_changed'))
|
||||
url = wrap_url(url, username=request.user.username, action='new_email',
|
||||
expired=datetime.now() + timedelta(seconds=ACCOUNT_AUTH_KEY_TIMEOUT),
|
||||
email=email)
|
||||
args = {'domain': ACCOUNT_DOMAIN, 'url': url, 'email': email,}
|
||||
if email_template(email, 'account/mail/new_email.txt', **args):
|
||||
return message(_('Check the mail please'))
|
||||
else:
|
||||
return message(_('Unfortunately we could not send you email in current time. Please, try later'))
|
||||
return {'form': form}
|
||||
|
||||
|
||||
@login_required
|
||||
@render_to('account/email_changed.html')
|
||||
def email_changed(request):
|
||||
return message(_('Your email has been changed to %s') % request.user.email)
|
0
apps/captcha/__init__.py
Normal file
0
apps/captcha/__init__.py
Normal file
BIN
apps/captcha/data/Vera.ttf
Normal file
BIN
apps/captcha/data/Vera.ttf
Normal file
Binary file not shown.
70
apps/captcha/fields.py
Normal file
70
apps/captcha/fields.py
Normal file
|
@ -0,0 +1,70 @@
|
|||
# Original: http://django-pantheon.googlecode.com/svn/trunk/pantheon/supernovaforms/
|
||||
|
||||
from django import forms
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from apps.captcha import util
|
||||
|
||||
class ImageWidget( forms.Widget ):
|
||||
"""
|
||||
Widget for rendering captcha image.
|
||||
"""
|
||||
|
||||
def render( self, name, value, attrs=None ):
|
||||
return forms.HiddenInput().render( name, value ) + u'<img src="%s"/> ' % value
|
||||
|
||||
|
||||
class CaptchaWidget( forms.MultiWidget ):
|
||||
"""
|
||||
Multiwidget for rendering captcha field.
|
||||
"""
|
||||
|
||||
def __init__(self, attrs = None):
|
||||
widgets = ( forms.HiddenInput(), ImageWidget(), forms.TextInput())
|
||||
super( CaptchaWidget, self ).__init__( widgets, attrs )
|
||||
|
||||
|
||||
def format_output(self, widgets):
|
||||
return u'<div class="captcha">%s<div class="captcha-image">%s</div><div class="captcha-input">%s</div></div>' % (widgets[0], widgets[1], widgets[2])
|
||||
|
||||
|
||||
def decompress(self, value):
|
||||
captcha_id = util.generate_captcha()
|
||||
url = reverse('apps.captcha.views.captcha_image', args=[captcha_id])
|
||||
return (captcha_id, url, '')
|
||||
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
# None value forces call to decompress
|
||||
# which will generate new captcha
|
||||
return super(CaptchaWidget, self).render(name, None, attrs)
|
||||
|
||||
|
||||
class CaptchaField(forms.Field):
|
||||
"""
|
||||
Captcha field.
|
||||
"""
|
||||
|
||||
widget = CaptchaWidget
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CaptchaField, self).__init__(*args, **kwargs)
|
||||
self.label = _('Human test')
|
||||
self.help_text = _('Please, enter the word on the picture')
|
||||
|
||||
|
||||
def clean(self, values):
|
||||
"""
|
||||
Test the solution
|
||||
|
||||
If succes delete the solution.
|
||||
"""
|
||||
|
||||
id, url, solution = values
|
||||
if not util.test_solution(id, solution):
|
||||
raise forms.ValidationError(_('Incorrect answer'))
|
||||
else:
|
||||
util.delete_solution(id)
|
||||
return values
|
6
apps/captcha/models.py
Normal file
6
apps/captcha/models.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from django.db import models
|
||||
|
||||
class Solution(models.Model):
|
||||
hash = models.CharField('Hash', max_length=40)
|
||||
value = models.CharField('Value', max_length=20)
|
||||
ctime = models.DateTimeField('Date created', blank=True, auto_now_add=True)
|
8
apps/captcha/urls.py
Normal file
8
apps/captcha/urls.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from django.conf.urls.defaults import *
|
||||
|
||||
from apps.captcha import views
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^captcha/(?P<captcha_id>\w+)/$', views.captcha_image),
|
||||
)
|
||||
|
68
apps/captcha/util.py
Normal file
68
apps/captcha/util.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sha
|
||||
import random
|
||||
from os.path import join, realpath, dirname
|
||||
try:
|
||||
import Image
|
||||
import ImageFornt
|
||||
import ImageDraw
|
||||
except ImportError:
|
||||
from PIL import Image, ImageFont, ImageDraw
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from apps.captcha.models import Solution
|
||||
|
||||
|
||||
#TODO: find similar function in stdlib or django utils
|
||||
def random_word(size=6):
|
||||
"""
|
||||
Generate random alphanumeric word
|
||||
"""
|
||||
|
||||
chars = "abcdefghjkmnpqrstuvwzyz23456789"
|
||||
return ''.join(random.choice(chars) for x in xrange(size))
|
||||
|
||||
|
||||
def generate_captcha():
|
||||
"""
|
||||
Generate random solution and save it to database
|
||||
"""
|
||||
|
||||
solution = random_word()
|
||||
hash = sha.new(solution + settings.SECRET_KEY).hexdigest()
|
||||
Solution.objects.create(value=solution, hash=hash)
|
||||
return hash
|
||||
|
||||
|
||||
def test_solution(captcha_id, solution):
|
||||
"""
|
||||
Compare the given answer with answer stored in database.
|
||||
"""
|
||||
qs = Solution.objects.filter(hash=captcha_id, value=solution)
|
||||
return qs.count() > 0
|
||||
|
||||
|
||||
def delete_solution(capthca_id):
|
||||
Solution.objects.filter(hash=captcha_id).delete()
|
||||
|
||||
def render(captcha_id, output):
|
||||
"""
|
||||
Generate image and save it to output stream.
|
||||
"""
|
||||
|
||||
try:
|
||||
solution_value = Solution.objects.filter(hash=captcha_id)[0].value
|
||||
except IndexError:
|
||||
raise ValueError('Invalid captcha ID')
|
||||
|
||||
fgcolor = getattr(settings, 'CAPTCHA_FG_COLOR', '#ffffff')
|
||||
bgcolor = getattr(settings, 'CAPTCHA_BG_COLOR', '#000000')
|
||||
font = ImageFont.truetype(join(dirname(realpath(__file__)),
|
||||
'data', 'Vera.ttf'), 25)
|
||||
dim = font.getsize(solution_value)
|
||||
img = Image.new('RGB', (dim[0] + 20, dim[1] + 10), bgcolor)
|
||||
draw = ImageDraw.Draw(img)
|
||||
draw.text((10, 5), solution_value, font=font, fill=fgcolor)
|
||||
img.save(output, format='JPEG')
|
17
apps/captcha/views.py
Normal file
17
apps/captcha/views.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.http import HttpResponse, HttpResponseNotFound
|
||||
|
||||
from apps.captcha import util
|
||||
|
||||
def captcha_image(request, captcha_id):
|
||||
"""
|
||||
Generate and save image to response.
|
||||
"""
|
||||
|
||||
response = HttpResponse(mimetype='image/jpeg')
|
||||
try:
|
||||
util.render(captcha_id, response)
|
||||
except ValueError:
|
||||
return HttpResponseNotFound('Invalid captcha ID')
|
||||
else:
|
||||
return response
|
0
apps/forum/__init__.py
Normal file
0
apps/forum/__init__.py
Normal file
47
apps/forum/admin.py
Normal file
47
apps/forum/admin.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
# -*- coding: utf-8
|
||||
from django.contrib import admin
|
||||
from apps.forum.models import Category, Forum, Topic, Post, Profile, Read, Reputation, Report
|
||||
|
||||
class CategoryAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'position', 'forum_count']
|
||||
|
||||
class ForumAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'category', 'position', 'topic_count']
|
||||
|
||||
class TopicAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'forum', 'created', 'head', 'post_count']
|
||||
search_fields = ['name']
|
||||
|
||||
class PostAdmin(admin.ModelAdmin):
|
||||
list_display = ['topic', 'user', 'created', 'updated', 'summary']
|
||||
search_fields = ['body']
|
||||
|
||||
class ProfileAdmin(admin.ModelAdmin):
|
||||
list_display = ['user', 'status', 'time_zone', 'location', 'language']
|
||||
|
||||
class ReadAdmin(admin.ModelAdmin):
|
||||
list_display = ['user', 'topic', 'time']
|
||||
|
||||
class ReputationAdmin(admin.ModelAdmin):
|
||||
list_display = ['from_user', 'to_user', 'topic', 'sign', 'time', 'reason']
|
||||
|
||||
class ReportAdmin(admin.ModelAdmin):
|
||||
list_display = ['reported_by', 'post', 'zapped', 'zapped_by', 'created', 'reason']
|
||||
|
||||
admin.site.register(Category, CategoryAdmin)
|
||||
admin.site.register(Forum, ForumAdmin)
|
||||
admin.site.register(Topic, TopicAdmin)
|
||||
admin.site.register(Post, PostAdmin)
|
||||
admin.site.register(Profile, ProfileAdmin)
|
||||
admin.site.register(Read, ReadAdmin)
|
||||
admin.site.register(Reputation, ReputationAdmin)
|
||||
admin.site.register(Report, ReportAdmin)
|
||||
|
||||
#admin.site.unregister(Category)
|
||||
#admin.site.unregister(Forum)
|
||||
#admin.site.unregister(Topic)
|
||||
#admin.site.unregister(Post)
|
||||
#admin.site.unregister(Profile)
|
||||
#admin.site.unregister(Read)
|
||||
#admin.site.unregister(Reputation)
|
||||
#admin.site.unregister(Report)
|
107
apps/forum/feeds.py
Normal file
107
apps/forum/feeds.py
Normal file
|
@ -0,0 +1,107 @@
|
|||
from django.contrib.syndication.feeds import Feed, FeedDoesNotExist
|
||||
from django.utils.feedgenerator import Atom1Feed
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from apps.forum.models import Post, Topic, Forum, Category
|
||||
|
||||
class ForumFeed(Feed):
|
||||
feed_type = Atom1Feed
|
||||
|
||||
def link(self):
|
||||
return reverse('apps.forum.views.index')
|
||||
|
||||
def item_guid(self, obj):
|
||||
return str(obj.id)
|
||||
|
||||
def item_pubdate(self, obj):
|
||||
return obj.created
|
||||
|
||||
class LastPosts(ForumFeed):
|
||||
title = _('Latest posts on forum')
|
||||
description = _('Latest posts on forum')
|
||||
title_template = 'forum/feeds/posts_title.html'
|
||||
description_template = 'forum/feeds/posts_description.html'
|
||||
|
||||
def items(self):
|
||||
return Post.objects.order_by('-created')[:15]
|
||||
|
||||
|
||||
class LastTopics(ForumFeed):
|
||||
title = _('Latest topics on forum')
|
||||
description = _('Latest topics on forum')
|
||||
title_template = 'forum/feeds/topics_title.html'
|
||||
description_template = 'forum/feeds/topics_description.html'
|
||||
|
||||
def items(self):
|
||||
return Topic.objects.order_by('-created')[:15]
|
||||
|
||||
class LastPostsOnTopic(ForumFeed):
|
||||
title_template = 'forum/feeds/posts_title.html'
|
||||
description_template = 'forum/feeds/posts_description.html'
|
||||
|
||||
def get_object(self, topics):
|
||||
if len(topics) != 1:
|
||||
raise ObjectDoesNotExist
|
||||
return Topic.objects.get(id=topics[0])
|
||||
|
||||
def title(self, obj):
|
||||
return _('Latest posts on %s topic' % obj.name)
|
||||
|
||||
def link(self, obj):
|
||||
if not obj:
|
||||
raise FeedDoesNotExist
|
||||
return obj.get_absolute_url()
|
||||
|
||||
def description(self, obj):
|
||||
return _('Latest posts on %s topic' % obj.name)
|
||||
|
||||
def items(self, obj):
|
||||
return Post.objects.filter(topic__id__exact=obj.id).order_by('-created')[:15]
|
||||
|
||||
|
||||
class LastPostsOnForum(ForumFeed):
|
||||
title_template = 'forum/feeds/posts_title.html'
|
||||
description_template = 'forum/feeds/posts_description.html'
|
||||
|
||||
def get_object(self, forums):
|
||||
if len(forums) != 1:
|
||||
raise ObjectDoesNotExist
|
||||
return Forum.objects.get(id=forums[0])
|
||||
|
||||
def title(self, obj):
|
||||
return _('Latest posts on %s forum' % obj.name)
|
||||
|
||||
def link(self, obj):
|
||||
if not obj:
|
||||
raise FeedDoesNotExist
|
||||
return obj.get_absolute_url()
|
||||
|
||||
def description(self, obj):
|
||||
return _('Latest posts on %s forum' % obj.name)
|
||||
|
||||
def items(self, obj):
|
||||
return Post.objects.filter(topic__forum__id__exact=obj.id).order_by('-created')[:15]
|
||||
|
||||
class LastPostsOnCategory(ForumFeed):
|
||||
title_template = 'forum/feeds/posts_title.html'
|
||||
description_template = 'forum/feeds/posts_description.html'
|
||||
|
||||
def get_object(self, categories):
|
||||
if len(categories) != 1:
|
||||
raise ObjectDoesNotExist
|
||||
return Category.objects.get(id=categories[0])
|
||||
|
||||
def title(self, obj):
|
||||
return _('Latest posts on %s category' % obj.name)
|
||||
|
||||
def link(self, obj):
|
||||
if not obj:
|
||||
raise FeedDoesNotExist
|
||||
return obj.get_absolute_url()
|
||||
|
||||
def description(self, obj):
|
||||
return _('Latest posts on %s category' % obj.name)
|
||||
|
||||
def items(self, obj):
|
||||
return Post.objects.filter(topic__forum__category__id__exact=obj.id).order_by('-created')[:15]
|
88
apps/forum/fields.py
Normal file
88
apps/forum/fields.py
Normal file
|
@ -0,0 +1,88 @@
|
|||
"""
|
||||
Details about AutoOneToOneField:
|
||||
http://softwaremaniacs.org/blog/2007/03/07/auto-one-to-one-field/
|
||||
"""
|
||||
|
||||
from StringIO import StringIO
|
||||
import logging
|
||||
|
||||
from django.db.models import OneToOneField
|
||||
from django.db.models.fields.related import SingleRelatedObjectDescriptor
|
||||
from django.db import models
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
|
||||
|
||||
class AutoSingleRelatedObjectDescriptor(SingleRelatedObjectDescriptor):
|
||||
def __get__(self, instance, instance_type=None):
|
||||
try:
|
||||
return super(AutoSingleRelatedObjectDescriptor, self).__get__(instance, instance_type)
|
||||
except self.related.model.DoesNotExist:
|
||||
obj = self.related.model(**{self.related.field.name: instance})
|
||||
obj.save()
|
||||
return obj
|
||||
|
||||
|
||||
class AutoOneToOneField(OneToOneField):
|
||||
"""
|
||||
OneToOneField creates dependent object on first request from parent object
|
||||
if dependent oject has not created yet.
|
||||
"""
|
||||
|
||||
def contribute_to_related_class(self, cls, related):
|
||||
setattr(cls, related.get_accessor_name(), AutoSingleRelatedObjectDescriptor(related))
|
||||
#if not cls._meta.one_to_one_field:
|
||||
# cls._meta.one_to_one_field = self
|
||||
|
||||
|
||||
class ExtendedImageField(models.ImageField):
|
||||
"""
|
||||
Extended ImageField that can resize image before saving it.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.width = kwargs.pop('width', None)
|
||||
self.height = kwargs.pop('height', None)
|
||||
super(ExtendedImageField, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
def save_form_data(self, instance, data):
|
||||
if data and self.width and self.height:
|
||||
content = self.resize_image(data.read(), width=self.width, height=self.height)
|
||||
data = SimpleUploadedFile(data.name, content, data.content_type)
|
||||
super(ExtendedImageField, self).save_form_data(instance, data)
|
||||
|
||||
|
||||
def resize_image(self, rawdata, width, height):
|
||||
"""
|
||||
Resize image to fit it into (width, height) box.
|
||||
"""
|
||||
|
||||
try:
|
||||
import Image
|
||||
except ImportError:
|
||||
from PIL import Image
|
||||
|
||||
image = Image.open(StringIO(rawdata))
|
||||
try:
|
||||
oldw, oldh = image.size
|
||||
|
||||
# do nothing if width and height are correct
|
||||
#if oldw == width and oldh == height:
|
||||
#print 'image already OK!'
|
||||
#return rawdata
|
||||
|
||||
if oldw >= oldh:
|
||||
x = int(round((oldw - oldh) / 2.0))
|
||||
image = image.crop((x, 0, (x + oldh) - 1, oldh - 1))
|
||||
else:
|
||||
y = int(round((oldh - oldw) / 2.0))
|
||||
image = image.crop((0, y, oldw - 1, (y + oldw) - 1))
|
||||
image = image.resize((width, height), resample=Image.ANTIALIAS)
|
||||
except Exception, err:
|
||||
logging.error(err)
|
||||
return ''
|
||||
|
||||
string = StringIO()
|
||||
image.save(string, format='PNG')
|
||||
return string.getvalue()
|
415
apps/forum/forms.py
Normal file
415
apps/forum/forms.py
Normal file
|
@ -0,0 +1,415 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from apps.forum.models import Topic, Post, Profile, Reputation, Report, PrivateMessage, Forum
|
||||
from apps.forum.markups import mypostmarkup
|
||||
|
||||
SORT_USER_BY_CHOICES = (
|
||||
('username', _(u'Username')),
|
||||
('registered', _(u'Registered')),
|
||||
('num_posts', _(u'No. of posts')),
|
||||
)
|
||||
|
||||
SORT_POST_BY_CHOICES = (
|
||||
('0', _(u'Post time')),
|
||||
('1', _(u'Author')),
|
||||
('2', _(u'Subject')),
|
||||
('3', _(u'Forum')),
|
||||
)
|
||||
|
||||
SORT_DIR_CHOICES = (
|
||||
('ASC', _(u'Ascending')),
|
||||
('DESC', _(u'Descending')),
|
||||
)
|
||||
|
||||
SHOW_AS_CHOICES = (
|
||||
('topics', _(u'Topics')),
|
||||
('posts', _(u'Posts')),
|
||||
)
|
||||
|
||||
SEARCH_IN_CHOICES = (
|
||||
('all', _(u'Message text and topic subject')),
|
||||
('message', _(u'Message text only')),
|
||||
('topic', _(u'Topic subject only')),
|
||||
)
|
||||
|
||||
class AddPostForm(forms.ModelForm):
|
||||
name = forms.CharField(label=_('Subject'),
|
||||
widget=forms.TextInput(attrs={'size':'115'}))
|
||||
|
||||
class Meta:
|
||||
model = Post
|
||||
fields = ['body']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop('user', None)
|
||||
self.topic = kwargs.pop('topic', None)
|
||||
self.forum = kwargs.pop('forum', None)
|
||||
self.ip = kwargs.pop('ip', None)
|
||||
super(AddPostForm, self).__init__(*args, **kwargs)
|
||||
if self.topic:
|
||||
self.fields['name'].widget = forms.HiddenInput()
|
||||
self.fields['name'].required = False
|
||||
self.fields['body'].widget = forms.Textarea(attrs={'class':'bbcode', 'rows':'20', 'cols':'95'})
|
||||
|
||||
|
||||
|
||||
def save(self):
|
||||
if self.forum:
|
||||
topic = Topic(forum=self.forum,
|
||||
user=self.user,
|
||||
name=self.cleaned_data['name'])
|
||||
topic.save()
|
||||
else:
|
||||
topic = self.topic
|
||||
|
||||
post = Post(topic=topic, user=self.user, user_ip=self.ip,
|
||||
markup='bbcode',
|
||||
body=self.cleaned_data['body'])
|
||||
post.save()
|
||||
return post
|
||||
|
||||
class EssentialsProfileForm(forms.ModelForm):
|
||||
username = forms.CharField(label=_('Username'))
|
||||
email = forms.CharField(label=_('E-mail'))
|
||||
|
||||
class Meta:
|
||||
model = Profile
|
||||
fields = ['time_zone', 'language']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop('user', None)
|
||||
super(EssentialsProfileForm, self).__init__(*args, **kwargs)
|
||||
self.fields['username'].initial = self.user.username
|
||||
self.fields['email'].initial = self.user.email
|
||||
|
||||
def save(self):
|
||||
user = get_object_or_404(User, username=self.user)
|
||||
profile = get_object_or_404(Profile, user=self.user)
|
||||
if self.cleaned_data:
|
||||
user.username = self.cleaned_data['username']
|
||||
user.email = self.cleaned_data['email']
|
||||
profile.time_zone = self.cleaned_data['time_zone']
|
||||
profile.language = self.cleaned_data['language']
|
||||
user.save()
|
||||
return profile.save()
|
||||
|
||||
|
||||
class PersonalProfileForm(forms.ModelForm):
|
||||
name = forms.CharField(label=_('Real name'))
|
||||
|
||||
class Meta:
|
||||
model = Profile
|
||||
fields = ['status', 'location', 'site']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop('user', None)
|
||||
super(PersonalProfileForm, self).__init__(*args, **kwargs)
|
||||
self.fields['name'].initial = "%s %s" % (self.user.first_name, self.user.last_name)
|
||||
|
||||
def save(self):
|
||||
user = get_object_or_404(User, username=self.user)
|
||||
profile = get_object_or_404(Profile, user=self.user)
|
||||
profile.status = self.cleaned_data['status']
|
||||
profile.location = self.cleaned_data['location']
|
||||
profile.site = self.cleaned_data['site']
|
||||
if self.cleaned_data['name']:
|
||||
if len(self.cleaned_data['name'].split()) > 1:
|
||||
user.first_name, user.last_name = self.cleaned_data['name'].split()
|
||||
else:
|
||||
user.first_name = self.cleaned_data['name'].split()[0]
|
||||
user.last_name = ''
|
||||
user.save()
|
||||
return profile.save()
|
||||
|
||||
class MessagingProfileForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Profile
|
||||
fields = ['jabber', 'icq', 'msn', 'aim', 'yahoo']
|
||||
|
||||
class PersonalityProfileForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Profile
|
||||
fields = ['show_avatar', 'signature']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PersonalityProfileForm, self).__init__(*args, **kwargs)
|
||||
self.fields['signature'].widget = forms.Textarea(attrs={'class':'bbcode', 'rows':'10', 'cols':'75'})
|
||||
|
||||
def save(self):
|
||||
profile = super(PersonalityProfileForm, self).save(commit=False)
|
||||
profile.signature = mypostmarkup.markup(profile.signature, auto_urls=False)
|
||||
profile.save()
|
||||
return profile
|
||||
|
||||
class DisplayProfileForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Profile
|
||||
fields = ['theme']
|
||||
|
||||
class PrivacyProfileForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Profile
|
||||
fields = ['privacy_permission']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PrivacyProfileForm, self).__init__(*args, **kwargs)
|
||||
self.fields['privacy_permission'].widget = forms.RadioSelect(
|
||||
choices=self.fields['privacy_permission'].choices
|
||||
)
|
||||
|
||||
#class AdminProfileForm(forms.Form):
|
||||
# forums = forms.CharField(label=_('Forums'))
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# self.user = kwargs.pop('user', None)
|
||||
# super(AdminProfileForm, self).__init__(*args, **kwargs)
|
||||
# forums = [(forum, forum) for forum in Forum.objects.all()]
|
||||
# self.fields['forums'].widget = forms.CheckboxSelectMultiple(
|
||||
# choices=forums
|
||||
# )
|
||||
#
|
||||
# def save(self):
|
||||
# print self.forums
|
||||
# return self.forums
|
||||
|
||||
class UploadAvatarForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Profile
|
||||
fields = ['avatar']
|
||||
|
||||
class EditPostForm(forms.ModelForm):
|
||||
name = forms.CharField(required=False, label=_('Subject'),
|
||||
widget=forms.TextInput(attrs={'size':'115'}))
|
||||
|
||||
class Meta:
|
||||
model = Post
|
||||
fields = ['body']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.topic = kwargs.pop('topic', None)
|
||||
super(EditPostForm, self).__init__(*args, **kwargs)
|
||||
self.fields['name'].initial = self.topic
|
||||
self.fields['body'].widget = forms.Textarea(attrs={'class':'bbcode'})
|
||||
|
||||
def save(self):
|
||||
post = super(EditPostForm, self).save(commit=False)
|
||||
post.updated = datetime.now()
|
||||
post.save()
|
||||
return post
|
||||
|
||||
|
||||
class UserSearchForm(forms.Form):
|
||||
username = forms.CharField(required=False)
|
||||
#show_group = forms.ChoiceField(choices=SHOW_GROUP_CHOICES)
|
||||
sort_by = forms.ChoiceField(choices=SORT_USER_BY_CHOICES)
|
||||
sort_dir = forms.ChoiceField(choices=SORT_DIR_CHOICES)
|
||||
|
||||
def filter(self, qs):
|
||||
if self.is_valid():
|
||||
username = self.cleaned_data['username']
|
||||
#show_group = self.cleaned_data['show_group']
|
||||
sort_by = self.cleaned_data['sort_by']
|
||||
sort_dir = self.cleaned_data['sort_dir']
|
||||
if sort_by=='username':
|
||||
if sort_dir=='ASC':
|
||||
return qs.filter(username__contains=username).order_by('username')
|
||||
elif sort_dir=='DESC':
|
||||
return qs.filter(username__contains=username).order_by('-username')
|
||||
elif sort_by=='registered':
|
||||
if sort_dir=='ASC':
|
||||
return qs.filter(username__contains=username).order_by('date_joined')
|
||||
elif sort_dir=='DESC':
|
||||
return qs.filter(username__contains=username).order_by('-date_joined')
|
||||
elif sort_by=='num_posts':
|
||||
if sort_dir=='ASC':
|
||||
#qs = qs.filter(username__contains=username).order_by('posts')
|
||||
qs.query.group_by = ['username']
|
||||
print qs
|
||||
return qs
|
||||
elif sort_dir=='DESC':
|
||||
#qs = qs.filter(username__contains=username).order_by('-posts')
|
||||
qs.query.group_by = ['username']
|
||||
print qs
|
||||
return qs
|
||||
else:
|
||||
return qs
|
||||
|
||||
class PostSearchForm(forms.Form):
|
||||
keywords = forms.CharField(required=False, label=_('Keyword search'),
|
||||
widget=forms.TextInput(attrs={'size':'40', 'maxlength':'100'}))
|
||||
author = forms.CharField(required=False, label=_('Author search'),
|
||||
widget=forms.TextInput(attrs={'size':'25', 'maxlength':'25'}))
|
||||
forum = forms.CharField(required=False, label=_('Forum'))
|
||||
search_in = forms.ChoiceField(choices=SEARCH_IN_CHOICES, label=_('Search in'))
|
||||
sort_by = forms.ChoiceField(choices=SORT_POST_BY_CHOICES, label=_('Sort by'))
|
||||
sort_dir = forms.ChoiceField(choices=SORT_DIR_CHOICES, label=_('Sort order'))
|
||||
show_as = forms.ChoiceField(choices=SHOW_AS_CHOICES, label=_('Show results as'))
|
||||
|
||||
def filter(self, qs):
|
||||
if self.is_valid():
|
||||
keywords = self.cleaned_data['keywords']
|
||||
author = self.cleaned_data['author']
|
||||
forum = self.cleaned_data['forum']
|
||||
search_in = self.cleaned_data['search_in']
|
||||
sort_by = self.cleaned_data['sort_by']
|
||||
sort_dir = self.cleaned_data['sort_dir']
|
||||
|
||||
if sort_by=='0':
|
||||
if sort_dir=='ASC':
|
||||
if search_in=='all':
|
||||
return qs.filter(Q(topic__name__contains=keywords)).order_by('created')
|
||||
elif search_in=='message':
|
||||
return qs.filter(body__contains=keywords, user__username__contains=author).order_by('created')
|
||||
elif search_in=='topic':
|
||||
return qs.filter(topic__contains=keywords, user__username__contains=author).order_by('created')
|
||||
elif sort_dir=='DESC':
|
||||
if search_in=='all':
|
||||
return qs.filter(Q(topic__contains=keywords) | Q(body__contains=keywords) & Q(user__username__contains=author)).order_by('-created')
|
||||
elif search_in=='message':
|
||||
return qs.filter(body__contains=keywords, user__username__contains=author).order_by('-created')
|
||||
elif search_in=='topic':
|
||||
return qs.filter(topic__contains=keywords, user__username__contains=author).order_by('-created')
|
||||
elif sort_by=='1':
|
||||
if sort_dir=='ASC':
|
||||
if search_in=='all':
|
||||
return qs.filter(Q(topic__contains=keywords) | Q(body__contains=keywords) & Q(user__username__contains=author)).order_by('user')
|
||||
elif search_in=='message':
|
||||
return qs.filter(body__contains=keywords, user__username__contains=author).order_by('user')
|
||||
elif search_in=='topic':
|
||||
return qs.filter(topic__contains=keywords, user__username__contains=author).order_by('user')
|
||||
elif sort_dir=='DESC':
|
||||
if search_in=='all':
|
||||
return qs.filter(Q(topic__contains=keywords) | Q(body__contains=keywords) & Q(user__username__contains=author)).order_by('-user')
|
||||
elif search_in=='message':
|
||||
return qs.filter(body__contains=keywords, user__username__contains=author).order_by('-user')
|
||||
elif search_in=='topic':
|
||||
return qs.filter(topic__contains=keywords, user__username__contains=author).order_by('-user')
|
||||
elif sort_by=='2':
|
||||
if sort_dir=='ASC':
|
||||
if search_in=='all':
|
||||
return qs.filter((Q(topic__contains=keywords) | Q(body__contains=keywords)) & Q(user__username__contains=author)).order_by('topic')
|
||||
elif search_in=='message':
|
||||
return qs.filter(body__contains=keywords, user__username__contains=author).order_by('topic')
|
||||
elif search_in=='topic':
|
||||
return qs.filter(topic__contains=keywords, user__username__contains=author).order_by('topic')
|
||||
elif sort_dir=='DESC':
|
||||
if search_in=='all':
|
||||
return qs.filter((Q(topic__contains=keywords) | Q(body__contains=keywords)) & Q(user__username__contains=author)).order_by('-topic')
|
||||
elif search_in=='message':
|
||||
return qs.filter(body__contains=keywords, user__username__contains=author).order_by('-topic')
|
||||
elif search_in=='topic':
|
||||
return qs.filter(topic__contains=keywords, user__username__contains=author).order_by('-topic')
|
||||
elif sort_by=='3':
|
||||
if sort_dir=='ASC':
|
||||
if search_in=='all':
|
||||
return qs.filter((Q(topic__contains=keywords) | Q(body__contains=keywords)) & Q(user__username__contains=author)).order_by('topic__forum')
|
||||
elif search_in=='message':
|
||||
return qs.filter(body__contains=keywords, user__username__contains=author).order_by('topic__forum')
|
||||
elif search_in=='topic':
|
||||
return qs.filter(topic__contains=keywords, user__username__contains=author).order_by('topic__forum')
|
||||
elif sort_dir=='DESC':
|
||||
if search_in=='all':
|
||||
return qs.filter((Q(topic__contains=keywords) | Q(body__contains=keywords)) & Q(user__username__contains=author)).order_by('-topic__forum')
|
||||
elif search_in=='message':
|
||||
return qs.filter(body__contains=keywords, user__username__contains=author).order_by('-topic__forum')
|
||||
elif search_in=='topic':
|
||||
return qs.filter(topic__contains=keywords, user__username__contains=author).order_by('-topic__forum')
|
||||
else:
|
||||
return qs
|
||||
|
||||
|
||||
class ReputationForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Reputation
|
||||
fields = ['reason', 'topic', 'sign']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.from_user = kwargs.pop('from_user', None)
|
||||
self.to_user = kwargs.pop('to_user', None)
|
||||
self.topic = kwargs.pop('topic', None)
|
||||
self.sign = kwargs.pop('sign', None)
|
||||
super(ReputationForm, self).__init__(*args, **kwargs)
|
||||
self.fields['topic'].widget = forms.HiddenInput()
|
||||
self.fields['sign'].widget = forms.HiddenInput()
|
||||
self.fields['reason'].widget = forms.Textarea(attrs={'class':'bbcode'})
|
||||
|
||||
def clean_to_user(self):
|
||||
name = self.cleaned_data['to_user']
|
||||
try:
|
||||
user = User.objects.get(username=name)
|
||||
except User.DoesNotExist:
|
||||
raise forms.ValidationError(_('User with login %s does not exist') % name)
|
||||
else:
|
||||
return user
|
||||
|
||||
def save(self):
|
||||
reputation = super(ReputationForm, self).save(commit=False)
|
||||
reputation.from_user = self.from_user
|
||||
reputation.to_user = self.to_user
|
||||
reputation.time = datetime.now()
|
||||
reputation.save()
|
||||
return reputation
|
||||
|
||||
class MailToForm(forms.Form):
|
||||
subject = forms.CharField(label=_('Subject'),
|
||||
widget=forms.TextInput(attrs={'size':'75', 'maxlength':'70', 'class':'longinput'}))
|
||||
body = forms.CharField(required=False, label=_('Message'),
|
||||
widget=forms.Textarea(attrs={'rows':'10', 'cols':'75'}))
|
||||
|
||||
class ReportForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Report
|
||||
fields = ['reason', 'post']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.reported_by = kwargs.pop('reported_by', None)
|
||||
self.post = kwargs.pop('post', None)
|
||||
super(ReportForm, self).__init__(*args, **kwargs)
|
||||
self.fields['post'].widget = forms.HiddenInput()
|
||||
self.fields['post'].initial = self.post
|
||||
self.fields['reason'].widget = forms.Textarea(attrs={'rows':'10', 'cols':'75'})
|
||||
|
||||
def save(self):
|
||||
report = super(ReportForm, self).save(commit=False)
|
||||
report.created = datetime.now()
|
||||
report.reported_by = self.reported_by
|
||||
report.save()
|
||||
return report
|
||||
|
||||
class CreatePMForm(forms.ModelForm):
|
||||
recipient = forms.CharField(label=_('Recipient'))
|
||||
|
||||
class Meta:
|
||||
model = PrivateMessage
|
||||
fields = ['subject', 'body']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop('user', None)
|
||||
super(CreatePMForm, self).__init__(*args, **kwargs)
|
||||
self.fields.keyOrder = ['recipient', 'subject', 'body']
|
||||
self.fields['subject'].widget = widget=forms.TextInput(attrs={'size':'115'})
|
||||
self.fields['body'].widget = forms.Textarea(attrs={'class':'bbcode'})
|
||||
|
||||
def clean_recipient(self):
|
||||
name = self.cleaned_data['recipient']
|
||||
try:
|
||||
user = User.objects.get(username=name)
|
||||
except User.DoesNotExist:
|
||||
raise forms.ValidationError(_('User with login %s does not exist') % name)
|
||||
else:
|
||||
return user
|
||||
def save(self):
|
||||
pm = PrivateMessage(src_user=self.user, dst_user=self.cleaned_data['recipient'])
|
||||
pm = forms.save_instance(self, pm)
|
||||
return pm
|
0
apps/forum/lib/__init__.py
Normal file
0
apps/forum/lib/__init__.py
Normal file
257
apps/forum/lib/phpserialize.py
Normal file
257
apps/forum/lib/phpserialize.py
Normal file
|
@ -0,0 +1,257 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
r"""
|
||||
phpserialize
|
||||
~~~~~~~~~~~~
|
||||
|
||||
a port of the ``serialize`` and ``unserialize`` functions of
|
||||
php to python. This module implements the python serialization
|
||||
interface (eg: provides `dumps`, `loads` and similar functions).
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
>>> from phpserialize import *
|
||||
>>> obj = dumps("Hello World")
|
||||
>>> loads(obj)
|
||||
'Hello World'
|
||||
|
||||
Due to the fact that PHP doesn't know the concept of lists, lists
|
||||
are serialized like hash-maps in PHP. As a matter of fact the
|
||||
reverse value of a serialized list is a dict:
|
||||
|
||||
>>> loads(dumps(range(2)))
|
||||
{0: 0, 1: 1}
|
||||
|
||||
If you want to have a list again, you can use the `dict_to_list`
|
||||
helper function:
|
||||
|
||||
>>> dict_to_list(loads(dumps(range(2))))
|
||||
[0, 1]
|
||||
|
||||
It's also possible to convert into a tuple by using the `dict_to_tuple`
|
||||
function:
|
||||
|
||||
>>> dict_to_tuple(loads(dumps((1, 2, 3))))
|
||||
(1, 2, 3)
|
||||
|
||||
Another problem are unicode strings. By default unicode strings are
|
||||
encoded to 'utf-8' but not decoded on `unserialize`. The reason for
|
||||
this is that phpserialize can't guess if you have binary or text data
|
||||
in the strings:
|
||||
|
||||
>>> loads(dumps(u'Hello W\xf6rld'))
|
||||
'Hello W\xc3\xb6rld'
|
||||
|
||||
If you know that you have only text data of a known charset in the result
|
||||
you can decode strings by setting `decode_strings` to True when calling
|
||||
loads:
|
||||
|
||||
>>> loads(dumps(u'Hello W\xf6rld'), decode_strings=True)
|
||||
u'Hello W\xf6rld'
|
||||
|
||||
Dictionary keys are limited to strings and integers. `None` is converted
|
||||
into an empty string and floats and booleans into integers for PHP
|
||||
compatibility:
|
||||
|
||||
>>> loads(dumps({None: 14, 42.23: 'foo', True: [1, 2, 3]}))
|
||||
{'': 14, 1: {0: 1, 1: 2, 2: 3}, 42: 'foo'}
|
||||
|
||||
It also provides functions to read from file-like objects:
|
||||
|
||||
>>> from StringIO import StringIO
|
||||
>>> stream = StringIO('a:2:{i:0;i:1;i:1;i:2;}')
|
||||
>>> dict_to_list(load(stream))
|
||||
[1, 2]
|
||||
|
||||
And to write to those:
|
||||
|
||||
>>> stream = StringIO()
|
||||
>>> dump([1, 2], stream)
|
||||
>>> stream.getvalue()
|
||||
'a:2:{i:0;i:1;i:1;i:2;}'
|
||||
|
||||
Like `pickle` chaining of objects is supported:
|
||||
|
||||
>>> stream = StringIO()
|
||||
>>> dump([1, 2], stream)
|
||||
>>> dump("foo", stream)
|
||||
>>> stream.seek(0)
|
||||
>>> load(stream)
|
||||
{0: 1, 1: 2}
|
||||
>>> load(stream)
|
||||
'foo'
|
||||
|
||||
This feature however is not supported in PHP. PHP will only unserialize
|
||||
the first object.
|
||||
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
1.1
|
||||
- added `dict_to_list` and `dict_to_tuple`
|
||||
- added support for unicode
|
||||
- allowed chaining of objects like pickle does.
|
||||
|
||||
|
||||
:copyright: 2007-2008 by Armin Ronacher.
|
||||
license: BSD
|
||||
"""
|
||||
from StringIO import StringIO
|
||||
|
||||
__author__ = 'Armin Ronacher <armin.ronacher@active-4.com>'
|
||||
__version__ = '1.1'
|
||||
|
||||
|
||||
def dumps(data, charset='utf-8', errors='strict'):
|
||||
"""Return the PHP-serialized representation of the object as a string,
|
||||
instead of writing it to a file like `dump` does.
|
||||
"""
|
||||
def _serialize(obj, keypos):
|
||||
if keypos:
|
||||
if isinstance(obj, (int, long, float, bool)):
|
||||
return 'i:%i;' % obj
|
||||
if isinstance(obj, basestring):
|
||||
if isinstance(obj, unicode):
|
||||
obj = obj.encode(charset, errors)
|
||||
return 's:%i:"%s";' % (len(obj), obj)
|
||||
if obj is None:
|
||||
return 's:0:"";'
|
||||
raise TypeError('can\'t serialize %r as key' % type(obj))
|
||||
else:
|
||||
if obj is None:
|
||||
return 'N;'
|
||||
if isinstance(obj, bool):
|
||||
return 'b:%i;' % obj
|
||||
if isinstance(obj, (int, long)):
|
||||
return 'i:%s;' % obj
|
||||
if isinstance(obj, float):
|
||||
return 'd:%s;' % obj
|
||||
if isinstance(obj, basestring):
|
||||
if isinstance(obj, unicode):
|
||||
obj = obj.encode(charset, errors)
|
||||
return 's:%i:"%s";' % (len(obj), obj)
|
||||
if isinstance(obj, (list, tuple, dict)):
|
||||
out = []
|
||||
if isinstance(obj, dict):
|
||||
iterable = obj.iteritems()
|
||||
else:
|
||||
iterable = enumerate(obj)
|
||||
for key, value in iterable:
|
||||
out.append(_serialize(key, True))
|
||||
out.append(_serialize(value, False))
|
||||
return 'a:%i:{%s}' % (len(obj), ''.join(out))
|
||||
raise TypeError('can\'t serialize %r' % type(obj))
|
||||
return _serialize(data, False)
|
||||
|
||||
|
||||
def load(fp, charset='utf-8', errors='strict', decode_strings=False):
|
||||
"""Read a string from the open file object `fp` and interpret it as a
|
||||
data stream of PHP-serialized objects, reconstructing and returning
|
||||
the original object hierarchy.
|
||||
|
||||
`fp` must provide a `read()` method that takes an integer argument. Both
|
||||
method should return strings. Thus `fp` can be a file object opened for
|
||||
reading, a `StringIO` object, or any other custom object that meets this
|
||||
interface.
|
||||
|
||||
`load` will read exactly one object from the stream. See the docstring of
|
||||
the module for this chained behavior.
|
||||
"""
|
||||
def _expect(e):
|
||||
v = fp.read(len(e))
|
||||
if v != e:
|
||||
raise ValueError('failed expectation, expected %r got %r' % (e, v))
|
||||
|
||||
def _read_until(delim):
|
||||
buf = []
|
||||
while 1:
|
||||
char = fp.read(1)
|
||||
if char == delim:
|
||||
break
|
||||
elif not char:
|
||||
raise ValueError('unexpected end of stream')
|
||||
buf.append(char)
|
||||
return ''.join(buf)
|
||||
|
||||
def _unserialize():
|
||||
type_ = fp.read(1).lower()
|
||||
if type_ == 'n':
|
||||
_expect(';')
|
||||
return None
|
||||
if type_ in 'idb':
|
||||
_expect(':')
|
||||
data = _read_until(';')
|
||||
if type_ == 'i':
|
||||
return int(data)
|
||||
if type_ == 'd':
|
||||
return float(data)
|
||||
return int(data) != 0
|
||||
if type_ == 's':
|
||||
_expect(':')
|
||||
length = int(_read_until(':'))
|
||||
_expect('"')
|
||||
data = fp.read(length)
|
||||
_expect('"')
|
||||
if decode_strings:
|
||||
data = data.decode(charset, errors)
|
||||
_expect(';')
|
||||
return data
|
||||
if type_ == 'a':
|
||||
_expect(':')
|
||||
items = int(_read_until(':')) * 2
|
||||
_expect('{')
|
||||
result = {}
|
||||
last_item = Ellipsis
|
||||
for idx in xrange(items):
|
||||
item = _unserialize()
|
||||
if last_item is Ellipsis:
|
||||
last_item = item
|
||||
else:
|
||||
result[last_item] = item
|
||||
last_item = Ellipsis
|
||||
_expect('}')
|
||||
return result
|
||||
raise ValueError('unexpected opcode')
|
||||
|
||||
return _unserialize()
|
||||
|
||||
|
||||
def loads(data, charset='utf-8', errors='strict', decode_strings=False):
|
||||
"""Read a PHP-serialized object hierarchy from a string. Characters in the
|
||||
string past the object's representation are ignored.
|
||||
"""
|
||||
return load(StringIO(data), charset, errors, decode_strings)
|
||||
|
||||
|
||||
def dump(data, fp, charset='utf-8', errors='strict'):
|
||||
"""Write a PHP-serialized representation of obj to the open file object
|
||||
`fp`. Unicode strings are encoded to `charset` with the error handling
|
||||
of `errors`.
|
||||
|
||||
`fp` must have a `write()` method that accepts a single string argument.
|
||||
It can thus be a file object opened for writing, a `StringIO` object, or
|
||||
any other custom object that meets this interface.
|
||||
"""
|
||||
fp.write(dumps(data, charset, errors))
|
||||
|
||||
|
||||
def dict_to_list(d):
|
||||
"""Converts an ordered dict into a list."""
|
||||
try:
|
||||
return [d[x] for x in xrange(len(d))]
|
||||
except KeyError:
|
||||
raise ValueError('dict is not a sequence')
|
||||
|
||||
|
||||
def dict_to_tuple(d):
|
||||
"""Converts an ordered dict into a tuple."""
|
||||
return tuple(dict_to_list(d))
|
||||
|
||||
|
||||
serialize = dumps
|
||||
unserialize = loads
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
doctest.testmod()
|
BIN
apps/forum/locale/ru/LC_MESSAGES/django.mo
Normal file
BIN
apps/forum/locale/ru/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
374
apps/forum/locale/ru/LC_MESSAGES/django.po
Normal file
374
apps/forum/locale/ru/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,374 @@
|
|||
# Russian translation for pybb forum
|
||||
# Copyright (C) 2008 Grigoriy Petukhov
|
||||
# This file is distributed under the same license as the pybb package.
|
||||
# Grigoriy Petukhov <lizendir@gmail.com>, 2008
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: pybb\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2008-10-15 03:34+0700\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: feeds.py:18 feeds.py:19 templates/pybb/base.html:12
|
||||
msgid "Latest posts on forum"
|
||||
msgstr "Последние сообщения на форуме"
|
||||
|
||||
#: feeds.py:28 feeds.py:29 templates/pybb/base.html:13
|
||||
msgid "Latest topics on forum"
|
||||
msgstr "Последние топики на форуме"
|
||||
|
||||
#: forms.py:11 models.py:103
|
||||
msgid "Subject"
|
||||
msgstr "Заголовок"
|
||||
|
||||
#: models.py:20
|
||||
msgid "Russian"
|
||||
msgstr "Русский"
|
||||
|
||||
#: models.py:41 models.py:69
|
||||
msgid "Name"
|
||||
msgstr "Имя"
|
||||
|
||||
#: models.py:42 models.py:70
|
||||
msgid "Position"
|
||||
msgstr "Позиция"
|
||||
|
||||
#: models.py:46 models.py:68
|
||||
msgid "Category"
|
||||
msgstr "Категория"
|
||||
|
||||
#: models.py:47
|
||||
msgid "Categories"
|
||||
msgstr "Категории"
|
||||
|
||||
#: models.py:71
|
||||
msgid "Description"
|
||||
msgstr "Описание"
|
||||
|
||||
#: models.py:72
|
||||
msgid "Moderators"
|
||||
msgstr "Модераторы"
|
||||
|
||||
#: models.py:73 models.py:105 models.py:156
|
||||
msgid "Updated"
|
||||
msgstr "Дата обновления"
|
||||
|
||||
#: models.py:74 models.py:111
|
||||
msgid "Post count"
|
||||
msgstr "Количество сообщений"
|
||||
|
||||
#: models.py:78 models.py:102
|
||||
msgid "Forum"
|
||||
msgstr "Форум"
|
||||
|
||||
#: models.py:79
|
||||
msgid "Forums"
|
||||
msgstr "Форумы"
|
||||
|
||||
#: models.py:104 models.py:155
|
||||
msgid "Created"
|
||||
msgstr "Дата создания"
|
||||
|
||||
#: models.py:106 models.py:154 models.py:240 models.py:266
|
||||
msgid "User"
|
||||
msgstr "Пользователь"
|
||||
|
||||
#: models.py:107
|
||||
msgid "Views count"
|
||||
msgstr "Количество просмотров"
|
||||
|
||||
#: models.py:108
|
||||
msgid "Sticky"
|
||||
msgstr "Выделено"
|
||||
|
||||
#: models.py:109 templates/pybb/forum.html:29
|
||||
msgid "Closed"
|
||||
msgstr "Закрыто"
|
||||
|
||||
#: models.py:110
|
||||
msgid "Subscribers"
|
||||
msgstr "Подписчики"
|
||||
|
||||
#: models.py:115 models.py:153 models.py:267
|
||||
msgid "Topic"
|
||||
msgstr "Топик"
|
||||
|
||||
#: models.py:116 templates/pybb/quick_access.html:47
|
||||
msgid "Topics"
|
||||
msgstr "Топики"
|
||||
|
||||
#: models.py:157
|
||||
msgid "Markup"
|
||||
msgstr "Разметка"
|
||||
|
||||
#: models.py:158
|
||||
msgid "Message"
|
||||
msgstr "Сообщение"
|
||||
|
||||
#: models.py:159
|
||||
msgid "HTML version"
|
||||
msgstr "HTML версия"
|
||||
|
||||
#: models.py:160
|
||||
msgid "Text version"
|
||||
msgstr "Текстовая версия"
|
||||
|
||||
#: models.py:161
|
||||
msgid "User IP"
|
||||
msgstr "IP автора"
|
||||
|
||||
#: models.py:166
|
||||
msgid "Post"
|
||||
msgstr "Сообщение"
|
||||
|
||||
#: models.py:167 templates/pybb/quick_access.html:50
|
||||
msgid "Posts"
|
||||
msgstr "Сообщения"
|
||||
|
||||
#: models.py:241 templates/pybb/user.html:23
|
||||
msgid "Site"
|
||||
msgstr "Сайт"
|
||||
|
||||
#: models.py:242
|
||||
msgid "Jabber"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:243
|
||||
msgid "ICQ"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:244
|
||||
msgid "MSN"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:245
|
||||
msgid "AIM"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:246
|
||||
msgid "Yahoo"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:247 templates/pybb/user.html:20
|
||||
msgid "Location"
|
||||
msgstr "Откуда"
|
||||
|
||||
#: models.py:248
|
||||
msgid "Signature"
|
||||
msgstr "Подпись"
|
||||
|
||||
#: models.py:249
|
||||
msgid "Time zone"
|
||||
msgstr "Временная зона"
|
||||
|
||||
#: models.py:250
|
||||
msgid "Language"
|
||||
msgstr "Язык"
|
||||
|
||||
#: models.py:251
|
||||
msgid "Avatar"
|
||||
msgstr "Аватар"
|
||||
|
||||
#: models.py:252
|
||||
msgid "Show signatures"
|
||||
msgstr "Показывать подписи"
|
||||
|
||||
#: models.py:253
|
||||
msgid "Default markup"
|
||||
msgstr "Разметка по умолчанию"
|
||||
|
||||
#: models.py:256
|
||||
msgid "Profile"
|
||||
msgstr "Профиль"
|
||||
|
||||
#: models.py:257
|
||||
msgid "Profiles"
|
||||
msgstr "Профили"
|
||||
|
||||
#: models.py:268
|
||||
msgid "Time"
|
||||
msgstr "Время"
|
||||
|
||||
#: models.py:272
|
||||
msgid "Read"
|
||||
msgstr "Просмотр"
|
||||
|
||||
#: models.py:273
|
||||
msgid "Reads"
|
||||
msgstr "Просмотры"
|
||||
|
||||
#: subscription.py:23
|
||||
#, python-format
|
||||
msgid ""
|
||||
"New reply from %(username)s to topic that you have subscribed on.\n"
|
||||
"---\n"
|
||||
"%(message)s\n"
|
||||
"---\n"
|
||||
"See topic: %(post_url)s\n"
|
||||
"Unsubscribe %(unsubscribe_url)s"
|
||||
msgstr ""
|
||||
|
||||
#: templates/pybb/add_post.html:8 templates/pybb/add_post.html.py:10
|
||||
#: templates/pybb/category.html:7 templates/pybb/edit_post.html:7
|
||||
#: templates/pybb/forum.html:7 templates/pybb/topic.html:7
|
||||
msgid "Root"
|
||||
msgstr "Начало"
|
||||
|
||||
#: templates/pybb/add_post.html:14 templates/pybb/add_post.html.py:18
|
||||
#: templates/pybb/forum.html:41
|
||||
msgid "New topic"
|
||||
msgstr "Новый топик"
|
||||
|
||||
#: templates/pybb/add_post.html:14 templates/pybb/add_post.html.py:18
|
||||
#: templates/pybb/topic.html:98
|
||||
msgid "New reply"
|
||||
msgstr "Новый ответ"
|
||||
|
||||
#: templates/pybb/add_post.html:20 templates/pybb/topic.html:100
|
||||
msgid "Submit"
|
||||
msgstr "Отправить"
|
||||
|
||||
#: templates/pybb/base.html:58
|
||||
msgid "PYBB - django forum engine"
|
||||
msgstr "PYBB - движок форума на django"
|
||||
|
||||
#: templates/pybb/edit_post.html:10 templates/pybb/edit_post.html.py:14
|
||||
msgid "Editing the post"
|
||||
msgstr "Редактирование поста"
|
||||
|
||||
#: templates/pybb/edit_post.html:16 templates/pybb/edit_profile.html:15
|
||||
msgid "Save"
|
||||
msgstr "Сохранить"
|
||||
|
||||
#: templates/pybb/edit_profile.html:6 templates/pybb/edit_profile.html:13
|
||||
msgid "Profile editing"
|
||||
msgstr "Изменение профиля"
|
||||
|
||||
#: templates/pybb/edit_profile.html:10
|
||||
msgid "Change the password"
|
||||
msgstr "Изменить пароль"
|
||||
|
||||
#: templates/pybb/edit_profile.html:18
|
||||
msgid "Subscriptions on topics"
|
||||
msgstr "Уведомления"
|
||||
|
||||
#: templates/pybb/edit_profile.html:25
|
||||
msgid "delete"
|
||||
msgstr "удалить"
|
||||
|
||||
#: templates/pybb/forum.html:19
|
||||
msgid "Important"
|
||||
msgstr "Важно"
|
||||
|
||||
#: templates/pybb/head.html:11 templates/pybb/head.html.py:41
|
||||
#: templates/pybb/users.html:9
|
||||
msgid "Search"
|
||||
msgstr "Поиск"
|
||||
|
||||
#: templates/pybb/head.html:12 templates/pybb/quick_access.html:53
|
||||
#: templates/pybb/users.html:6
|
||||
msgid "Users"
|
||||
msgstr "Пользователи"
|
||||
|
||||
#: templates/pybb/head.html:14
|
||||
msgid "My profile"
|
||||
msgstr "Мой профиль"
|
||||
|
||||
#: templates/pybb/head.html:15
|
||||
msgid "Log out"
|
||||
msgstr "Выйти"
|
||||
|
||||
#: templates/pybb/head.html:16
|
||||
msgid "Logged in as "
|
||||
msgstr "Ваш логин"
|
||||
|
||||
#: templates/pybb/head.html:18
|
||||
msgid "Sign up"
|
||||
msgstr "Регистрация"
|
||||
|
||||
#: templates/pybb/head.html:19
|
||||
msgid "Log in"
|
||||
msgstr "Войти"
|
||||
|
||||
#: templates/pybb/head.html:42
|
||||
msgid "Search powered by Google"
|
||||
msgstr "Поиск осуществляется через Google"
|
||||
|
||||
#: templates/pybb/pagination.html:6
|
||||
msgid "Pages"
|
||||
msgstr "Страницы"
|
||||
|
||||
#: templates/pybb/quick_access.html:16
|
||||
msgid "Last topics"
|
||||
msgstr "Топики"
|
||||
|
||||
#: templates/pybb/quick_access.html:17
|
||||
msgid "Last posts"
|
||||
msgstr "Сообщения"
|
||||
|
||||
#: templates/pybb/quick_access.html:18 templates/pybb/user.html:14
|
||||
msgid "Statistics"
|
||||
msgstr "Статистика"
|
||||
|
||||
#: templates/pybb/topic.html:37
|
||||
msgid "Edit"
|
||||
msgstr "Редактировать"
|
||||
|
||||
#: templates/pybb/topic.html:41
|
||||
msgid "Are you sure you want to delete this post?"
|
||||
msgstr "Вы уверены, что хотите удалить это сообщение?"
|
||||
|
||||
#: templates/pybb/topic.html:41
|
||||
msgid "Delete"
|
||||
msgstr "Удалить"
|
||||
|
||||
#: templates/pybb/topic.html:56
|
||||
msgid "Edited"
|
||||
msgstr "Отредактировано"
|
||||
|
||||
#: templates/pybb/topic.html:74
|
||||
msgid "Unstick topic"
|
||||
msgstr "Убрать выделение"
|
||||
|
||||
#: templates/pybb/topic.html:76
|
||||
msgid "Stick topic"
|
||||
msgstr "Выделить топик"
|
||||
|
||||
#: templates/pybb/topic.html:80
|
||||
msgid "Open topic"
|
||||
msgstr "Открыть топик"
|
||||
|
||||
#: templates/pybb/topic.html:82
|
||||
msgid "Close topic"
|
||||
msgstr "Закрыть топик"
|
||||
|
||||
#: templates/pybb/topic.html:88
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Запретить уведомления"
|
||||
|
||||
#: templates/pybb/topic.html:90
|
||||
msgid "Subscribe"
|
||||
msgstr "Получать уведомления"
|
||||
|
||||
#: templates/pybb/user.html:15
|
||||
msgid "Number of posts"
|
||||
msgstr "Количество сообщений"
|
||||
|
||||
#: templates/pybb/user.html:16
|
||||
msgid "Number of topics"
|
||||
msgstr "Количество топиков"
|
||||
|
||||
#: templates/pybb/user.html:17
|
||||
msgid "Date of registration"
|
||||
msgstr "Дата регистрации"
|
||||
|
||||
#: templates/pybb/user.html:18
|
||||
msgid "Contacts"
|
||||
msgstr "Контакты"
|
0
apps/forum/management/__init__.py
Normal file
0
apps/forum/management/__init__.py
Normal file
0
apps/forum/management/commands/__init__.py
Normal file
0
apps/forum/management/commands/__init__.py
Normal file
300
apps/forum/management/commands/import_punbb.py
Normal file
300
apps/forum/management/commands/import_punbb.py
Normal file
|
@ -0,0 +1,300 @@
|
|||
import sqlalchemy as SA
|
||||
from sqlalchemy import sql
|
||||
from datetime import datetime
|
||||
import os
|
||||
from optparse import make_option
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from apps.forum.models import Category, Forum, Topic, Post, Profile
|
||||
from apps.forums.lib import phpserialize
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--user', help=u'Punbb DB username'),
|
||||
make_option('--password', help=u'Punbb DB password'),
|
||||
make_option('--host', default='localhost', help=u'Punbb DB host'),
|
||||
make_option('--port', help=u'Punbb DB port'),
|
||||
make_option('--encoding', default='cp1251', help=u'Punbb DB encoding'),
|
||||
make_option('--mysql-encoding', help=u'Punbb DB encoding. I can\'t explain this yet'),
|
||||
make_option('--engine', default='mysql', help=u'Punbb DB engine [postgres, mysql etc]'),
|
||||
make_option('--prefix', default='punbb_', help=u'Punbb DB tables prefix'),
|
||||
)
|
||||
help = u'Imports Punbb database. Attention: old contents of forum database will be removed'
|
||||
args = '<db name>'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if len(args) != 1:
|
||||
raise CommandError('Punbb database name required')
|
||||
else:
|
||||
DBNAME = args[0]
|
||||
ENCODING = options['encoding']
|
||||
MYSQL_ENCODING = options['mysql_encoding']
|
||||
PREFIX = options['prefix']
|
||||
|
||||
uri = '%s://' % options['engine']
|
||||
if options['user'] is not None:
|
||||
uri += options['user']
|
||||
if options['password'] is not None:
|
||||
uri += ':%s' % options['password']
|
||||
if options['host'] is not None:
|
||||
uri += '@%s' % options['host']
|
||||
if options['port'] is not None:
|
||||
uri += ':%s' % options['port']
|
||||
uri += '/%s' % DBNAME
|
||||
|
||||
if options['engine'] == 'mysql' and not MYSQL_ENCODING:
|
||||
uri += '?charset=%s' % ENCODING.replace('-', '')
|
||||
|
||||
engine = SA.create_engine(uri, convert_unicode=False)
|
||||
conn = engine.connect()
|
||||
|
||||
meta = SA.MetaData()
|
||||
meta.bind = engine
|
||||
|
||||
users_table = SA.Table(PREFIX + 'users', meta, autoload=True)
|
||||
cats_table = SA.Table(PREFIX + 'categories', meta, autoload=True)
|
||||
forums_table = SA.Table(PREFIX + 'forums', meta, autoload=True)
|
||||
topics_table = SA.Table(PREFIX + 'topics', meta, autoload=True)
|
||||
posts_table = SA.Table(PREFIX + 'posts', meta, autoload=True)
|
||||
groups_table = SA.Table(PREFIX + 'groups', meta, autoload=True)
|
||||
config_table = SA.Table(PREFIX + 'config', meta, autoload=True)
|
||||
subscriptions_table = SA.Table(PREFIX + 'subscriptions', meta, autoload=True)
|
||||
|
||||
def decode(data):
|
||||
if data is None:
|
||||
return None
|
||||
if options['engine'] != 'mysql' or MYSQL_ENCODING:
|
||||
return data.decode(ENCODING, 'replace')
|
||||
else:
|
||||
return data
|
||||
|
||||
# Import begins
|
||||
print 'Searching admin group'
|
||||
|
||||
ADMIN_GROUP = None
|
||||
for count, row in enumerate(conn.execute(sql.select([groups_table]))):
|
||||
if row['g_title'] == 'Administrators':
|
||||
print 'Admin group was found'
|
||||
ADMIN_GROUP = row['g_id']
|
||||
|
||||
if ADMIN_GROUP is None:
|
||||
print 'Admin group was NOT FOUND'
|
||||
|
||||
|
||||
print 'Importing users'
|
||||
users = {}
|
||||
User.objects.all().delete()
|
||||
|
||||
count = 0
|
||||
for count, row in enumerate(conn.execute(sql.select([users_table]))):
|
||||
joined = datetime.fromtimestamp(row['registered'])
|
||||
last_login = datetime.fromtimestamp(row['last_visit'])
|
||||
if len(row['password']) == 40:
|
||||
hash = 'sha1$$' + row['password']
|
||||
else:
|
||||
hash = 'md5$$' + row['password']
|
||||
user = User(username=decode(row['username']),
|
||||
email=row['email'],
|
||||
first_name=decode((row['realname'] or '')[:30]),
|
||||
date_joined=joined,
|
||||
last_login=last_login,
|
||||
password=hash
|
||||
)
|
||||
if row['group_id'] == ADMIN_GROUP:
|
||||
print u'Admin was found: %s' % row['username']
|
||||
user.is_superuser = True
|
||||
user.is_staff = True
|
||||
|
||||
try:
|
||||
user.save()
|
||||
except Exception, ex:
|
||||
print ex
|
||||
else:
|
||||
users[row['id']] = user
|
||||
|
||||
profile = user.forum_profile
|
||||
profile.jabber = decode(row['jabber'])
|
||||
profile.icq = decode(row['icq'])
|
||||
profile.yahoo = decode(row['yahoo'])
|
||||
profile.msn = decode(row['msn'])
|
||||
profile.aim = decode(row['aim'])
|
||||
profile.location = decode(row['location'])
|
||||
profile.signature = decode(row['signature'])
|
||||
profile.show_signatures = bool(row['show_sig'])
|
||||
profile.time_zone = row['timezone']
|
||||
|
||||
print 'Total: %d' % (count + 1)
|
||||
print 'Imported: %d' % len(users)
|
||||
print
|
||||
|
||||
print 'Importing categories'
|
||||
cats = {}
|
||||
Category.objects.all().delete()
|
||||
|
||||
count = 0
|
||||
for count, row in enumerate(conn.execute(sql.select([cats_table]))):
|
||||
cat = Category(name=decode(row['cat_name']),
|
||||
position=row['disp_position'])
|
||||
cat.save()
|
||||
cats[row['id']] = cat
|
||||
|
||||
print 'Total: %d' % (count + 1)
|
||||
print 'Imported: %d' % len(cats)
|
||||
print
|
||||
|
||||
print 'Importing forums'
|
||||
forums = {}
|
||||
moderators = {}
|
||||
Forum.objects.all().delete()
|
||||
|
||||
count = 0
|
||||
for count, row in enumerate(conn.execute(sql.select([forums_table]))):
|
||||
if row['last_post']:
|
||||
updated = datetime.fromtimestamp(row['last_post'])
|
||||
else:
|
||||
updated = None
|
||||
|
||||
forum = Forum(name=decode(row['forum_name']),
|
||||
position=row['disp_position'],
|
||||
description=decode(row['forum_desc'] or ''),
|
||||
category=cats[row['cat_id']])
|
||||
forum.save()
|
||||
forums[row['id']] = forum
|
||||
|
||||
forum._forum_updated = updated
|
||||
|
||||
if row['moderators']:
|
||||
for username in phpserialize.loads(row['moderators']).iterkeys():
|
||||
user = User.objects.get(username=username)
|
||||
forum.moderators.add(user)
|
||||
moderators[user.id] = user
|
||||
|
||||
print 'Total: %d' % (count + 1)
|
||||
print 'Imported: %d' % len(forums)
|
||||
print 'Total number of moderators: %d' % len(moderators)
|
||||
print
|
||||
|
||||
|
||||
print 'Importing topics'
|
||||
topics = {}
|
||||
moved_count = 0
|
||||
Topic.objects.all().delete()
|
||||
|
||||
count = 0
|
||||
for count, row in enumerate(conn.execute(sql.select([topics_table]))):
|
||||
created = datetime.fromtimestamp(row['posted'])
|
||||
updated = datetime.fromtimestamp(row['last_post'])
|
||||
|
||||
# Skip moved topics
|
||||
if row['moved_to']:
|
||||
moved_count += 1
|
||||
continue
|
||||
|
||||
username = decode(row['poster'])
|
||||
#testuser = users.values()[0]
|
||||
for id, testuser in users.iteritems():
|
||||
if testuser.username == username:
|
||||
user = testuser
|
||||
break
|
||||
|
||||
topic = Topic(name=decode(row['subject']),
|
||||
forum=forums[row['forum_id']],
|
||||
views=row['num_views'],
|
||||
sticky=bool(row['sticky']),
|
||||
closed=bool(row['closed']),
|
||||
user=user)
|
||||
topic.save()
|
||||
topic._forum_updated = updated
|
||||
topic._forum_created = created
|
||||
topic._forum_punbb_id = row['id']
|
||||
topic._forum_posts = 0
|
||||
#print topic._forum_updated
|
||||
topics[row['id']] = topic
|
||||
|
||||
print 'Total: %d' % (count + 1)
|
||||
print 'Imported: %d' % len(topics)
|
||||
print 'Moved: %d' % moved_count
|
||||
print
|
||||
|
||||
|
||||
print 'Importing posts'
|
||||
posts = {}
|
||||
Post.objects.all().delete()
|
||||
|
||||
imported = 0
|
||||
count = 0
|
||||
for count, row in enumerate(conn.execute(sql.select([posts_table]))):
|
||||
created = datetime.fromtimestamp(row['posted'])
|
||||
updated = row['edited'] and datetime.fromtimestamp(row['edited']) or None
|
||||
|
||||
if not row['poster_id'] in users:
|
||||
print 'post #%d, poster_id #%d does not exist' % (row['id'], row['poster_id'])
|
||||
continue
|
||||
|
||||
post = Post(topic=topics[row['topic_id']],
|
||||
created=created,
|
||||
updated=updated,
|
||||
markup='bbcode',
|
||||
user=users[row['poster_id']],
|
||||
user_ip=row['poster_ip'],
|
||||
body=decode(row['message']))
|
||||
|
||||
post.save()
|
||||
imported += 1
|
||||
topics[row['topic_id']]._forum_posts += 1
|
||||
|
||||
# Not actual hack
|
||||
## postmarkups feels bad on some posts :-/
|
||||
#try:
|
||||
#post.save()
|
||||
#except Exception, ex:
|
||||
#print post.id, ex
|
||||
#print decode(row['message'])
|
||||
#print
|
||||
#else:
|
||||
#imported += 1
|
||||
#topics[row['topic_id']]._forum_posts += 1
|
||||
##posts[row['id']] = topic
|
||||
|
||||
print 'Total: %d' % (count + 1)
|
||||
print 'Imported: %d' % imported
|
||||
print
|
||||
|
||||
|
||||
print 'Importing subscriptions'
|
||||
|
||||
count = 0
|
||||
for count, row in enumerate(conn.execute(sql.select([subscriptions_table]))):
|
||||
user = users[row['user_id']]
|
||||
topic = topics[row['topic_id']]
|
||||
topic.subscribers.add(user)
|
||||
|
||||
print 'Imported: %d' % count
|
||||
|
||||
|
||||
print 'Restoring topics updated and created values'
|
||||
for topic in topics.itervalues():
|
||||
topic.updated = topic._forum_updated
|
||||
topic.created = topic._forum_created
|
||||
topic.save()
|
||||
print
|
||||
|
||||
|
||||
print 'Restoring forums updated and created values'
|
||||
for forum in forums.itervalues():
|
||||
forum.updated = forum._forum_updated
|
||||
forum.save()
|
||||
print
|
||||
|
||||
|
||||
print 'Importing config'
|
||||
for row in conn.execute(sql.select([config_table])):
|
||||
if row['conf_name'] == 'o_announcement_message':
|
||||
value = decode(row['conf_value'])
|
||||
if value:
|
||||
open('forum_announcement.txt', 'w').write(value.encode('utf-8'))
|
||||
print 'Not empty announcement found and saved to forum_announcement.txt'
|
||||
print 'If you need announcement write FORUM_NOTICE = " ... text ... " to settings file'
|
45
apps/forum/management/commands/pybb_migrate.py
Normal file
45
apps/forum/management/commands/pybb_migrate.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
from optparse import make_option
|
||||
import os
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from apps import forum
|
||||
|
||||
class Command(BaseCommand):
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('-l', '--list', dest='list', action='store_true', default=False,
|
||||
help='Show all available migrations'),
|
||||
make_option('-e', '--exec', dest='migration', help='Execute the migration')
|
||||
)
|
||||
help = 'Execute migration scripts.'
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
if kwargs['list']:
|
||||
self.command_list()
|
||||
elif kwargs['migration']:
|
||||
self.command_migrate(kwargs['migration'])
|
||||
else:
|
||||
print 'Invalid options'
|
||||
|
||||
|
||||
def command_list(self):
|
||||
root = os.path.dirname(os.path.realpath(forum.__file__))
|
||||
dir = os.path.join(root, 'migrations')
|
||||
migs = []
|
||||
for fname in os.listdir(dir):
|
||||
if fname.endswith('.py'):
|
||||
mod_name = fname[:-3]
|
||||
mod = __import__('forum.migrations.%s' % mod_name,
|
||||
globals(), locals(), ['foobar'])
|
||||
if hasattr(mod, 'migrate'):
|
||||
migs.append((mod_name, mod))
|
||||
|
||||
migs = sorted(migs, lambda a, b: cmp(a[0], b[0]))
|
||||
for name, mig in migs:
|
||||
print '%s - %s' % (name, mig.DESCRIPTION)
|
||||
|
||||
|
||||
def command_migrate(self, mod_name):
|
||||
mod = __import__('forum.migrations.%s' % mod_name,
|
||||
globals(), locals(), ['foobar'])
|
||||
mod.migrate()
|
0
apps/forum/markups/__init__.py
Normal file
0
apps/forum/markups/__init__.py
Normal file
27
apps/forum/markups/mypostmarkup.py
Normal file
27
apps/forum/markups/mypostmarkup.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
import re
|
||||
|
||||
from apps.forum.markups import postmarkup
|
||||
|
||||
RE_FIRST_LF = re.compile('^\s*\r?\n')
|
||||
markup = postmarkup.create(exclude=['link', 'url', 'code'], use_pygments=False)
|
||||
|
||||
class LinkTagNoAnnotate(postmarkup.LinkTag):
|
||||
def annotate_link(self, domain):
|
||||
return ''
|
||||
|
||||
|
||||
class CodeTagNoBreak(postmarkup.CodeTag):
|
||||
def render_open(self, parser, node_index):
|
||||
|
||||
contents = self._escape(self.get_contents(parser))
|
||||
contents = RE_FIRST_LF.sub('', contents)
|
||||
self.skip_contents(parser)
|
||||
return '<pre><code>%s</code></pre>' % contents
|
||||
|
||||
def _escape(self, s):
|
||||
return postmarkup.PostMarkup.standard_replace_no_break(s.rstrip('\n'))
|
||||
|
||||
|
||||
markup.tag_factory.add_tag(LinkTagNoAnnotate, 'url')
|
||||
markup.tag_factory.add_tag(CodeTagNoBreak, 'code')
|
||||
|
1229
apps/forum/markups/postmarkup.py
Normal file
1229
apps/forum/markups/postmarkup.py
Normal file
File diff suppressed because it is too large
Load diff
9
apps/forum/middleware.py
Normal file
9
apps/forum/middleware.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
#from datetime import datetime
|
||||
#from django.http import HttpResponseRedirect
|
||||
from django.core.cache import cache
|
||||
from django.conf import settings
|
||||
#from django.core.urlresolvers import reverse
|
||||
|
||||
class LastLoginMiddleware(object):
|
||||
def process_request(self, request):
|
||||
cache.set(str(request.user.id), True, settings.FORUM_USER_ONLINE_TIMEOUT)
|
23
apps/forum/migrations/001_forum_updated.py
Normal file
23
apps/forum/migrations/001_forum_updated.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from django.db import connection
|
||||
|
||||
from apps.forum.models import Forum, Topic
|
||||
|
||||
DESCRIPTION = 'Add updated column to forum table'
|
||||
|
||||
def migrate():
|
||||
cur = connection.cursor()
|
||||
|
||||
print 'Altering forum table'
|
||||
cur.execute("ALTER TABLE forum_forum ADD updated DATETIME NULL")
|
||||
|
||||
print 'Fixing updated values of forums'
|
||||
for forum in Forum.objects.all():
|
||||
try:
|
||||
topic = forum.topics.all().order_by('-updated')[0]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
forum.updated = topic.updated
|
||||
forum.save()
|
||||
|
||||
|
24
apps/forum/migrations/002_post_count.py
Normal file
24
apps/forum/migrations/002_post_count.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
from django.db import connection
|
||||
|
||||
from apps.forum.models import Forum, Topic
|
||||
|
||||
DESCRIPTION = 'Add and populate post_count fields to Forum and Topic models'
|
||||
|
||||
def migrate():
|
||||
cur = connection.cursor()
|
||||
|
||||
print 'Altering forum table'
|
||||
cur.execute("ALTER TABLE forum_forum ADD post_count INT NOT NULL DEFAULT 0")
|
||||
|
||||
print 'Altering topic table'
|
||||
cur.execute("ALTER TABLE forum_topic ADD post_count INT NOT NULL DEFAULT 0")
|
||||
|
||||
print 'Populating post_count of topics'
|
||||
for topic in Topic.objects.all():
|
||||
topic.post_count = topic.posts.all().count()
|
||||
topic.save()
|
||||
|
||||
print 'Populating post_count of forums'
|
||||
for forum in Forum.objects.all():
|
||||
forum.post_count = sum(x.post_count for x in forum.topics.all())
|
||||
forum.save()
|
0
apps/forum/migrations/__init__.py
Normal file
0
apps/forum/migrations/__init__.py
Normal file
377
apps/forum/models.py
Normal file
377
apps/forum/models.py
Normal file
|
@ -0,0 +1,377 @@
|
|||
from datetime import datetime
|
||||
import os
|
||||
import os.path
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.html import escape, strip_tags
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
#from django.contrib.markup.templatetags.markup import markdown
|
||||
from markdown import Markdown
|
||||
|
||||
from apps.forum.markups import mypostmarkup
|
||||
from apps.forum.fields import AutoOneToOneField, ExtendedImageField
|
||||
from apps.forum.subscription import notify_subscribers
|
||||
from apps.forum.util import urlize
|
||||
|
||||
LANGUAGE_CHOICES = (
|
||||
('en', 'English'),
|
||||
('ru', 'Russian'),
|
||||
)
|
||||
|
||||
TZ_CHOICES = [(float(x[0]), x[1]) for x in (
|
||||
(-12, '-12'), (-11, '-11'), (-10, '-10'), (-9.5, '-09.5'), (-9, '-09'),
|
||||
(-8.5, '-08.5'), (-8, '-08 PST'), (-7, '-07 MST'), (-6, '-06 CST'),
|
||||
(-5, '-05 EST'), (-4, '-04 AST'), (-3.5, '-03.5'), (-3, '-03 ADT'),
|
||||
(-2, '-02'), (-1, '-01'), (0, '00 GMT'), (1, '+01 CET'), (2, '+02'),
|
||||
(3, '+03'), (3.5, '+03.5'), (4, '+04'), (4.5, '+04.5'), (5, '+05'),
|
||||
(5.5, '+05.5'), (6, '+06'), (6.5, '+06.5'), (7, '+07'), (8, '+08'),
|
||||
(9, '+09'), (9.5, '+09.5'), (10, '+10'), (10.5, '+10.5'), (11, '+11'),
|
||||
(11.5, '+11.5'), (12, '+12'), (13, '+13'), (14, '+14'),
|
||||
)]
|
||||
|
||||
SIGN_CHOICES = (
|
||||
(1, 'PLUS'),
|
||||
(-1, 'MINUS'),
|
||||
)
|
||||
|
||||
PRIVACY_CHOICES = (
|
||||
(0, _(u'Display your e-mail address.')),
|
||||
(1, _(u'Hide your e-mail address but allow form e-mail.')),
|
||||
(2, _(u'Hide your e-mail address and disallow form e-mail.')),
|
||||
)
|
||||
|
||||
MARKUP_CHOICES = (
|
||||
('bbcode', 'bbcode'),
|
||||
('markdown', 'markdown'),
|
||||
)
|
||||
|
||||
path = settings.PROJECT_ROOT + settings.MEDIA_URL + 'forum/themes/'
|
||||
THEME_CHOICES = [(theme, theme) for theme in os.listdir(path)
|
||||
if os.path.isdir(path + theme)]
|
||||
|
||||
class Category(models.Model):
|
||||
name = models.CharField(_('Name'), max_length=80)
|
||||
position = models.IntegerField(_('Position'), blank=True, default=0)
|
||||
|
||||
class Meta:
|
||||
ordering = ['position']
|
||||
verbose_name = _('Category')
|
||||
verbose_name_plural = _('Categories')
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
def forum_count(self):
|
||||
return self.forums.all().count()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('category', args=[self.id])
|
||||
|
||||
@property
|
||||
def topics(self):
|
||||
return Topic.objects.filter(forum__category=self).select_related()
|
||||
|
||||
@property
|
||||
def posts(self):
|
||||
return Post.objects.filter(topic__forum__category=self).select_related()
|
||||
|
||||
|
||||
class Forum(models.Model):
|
||||
category = models.ForeignKey(Category, related_name='forums', verbose_name=_('Category'))
|
||||
name = models.CharField(_('Name'), max_length=80)
|
||||
position = models.IntegerField(_('Position'), blank=True, default=0)
|
||||
description = models.TextField(_('Description'), blank=True, default='')
|
||||
moderators = models.ManyToManyField(User, blank=True, null=True, verbose_name=_('Moderators'))
|
||||
updated = models.DateTimeField(_('Updated'), null=True)
|
||||
post_count = models.IntegerField(_('Post count'), blank=True, default=0)
|
||||
|
||||
class Meta:
|
||||
ordering = ['position']
|
||||
verbose_name = _('Forum')
|
||||
verbose_name_plural = _('Forums')
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
def topic_count(self):
|
||||
return self.topics.all().count()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('forum', args=[self.id])
|
||||
|
||||
@property
|
||||
def posts(self):
|
||||
return Post.objects.filter(topic__forum=self).select_related()
|
||||
|
||||
@property
|
||||
def last_post(self):
|
||||
posts = self.posts.order_by('-created').select_related()
|
||||
try:
|
||||
return posts[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
class Topic(models.Model):
|
||||
forum = models.ForeignKey(Forum, related_name='topics', verbose_name=_('Forum'))
|
||||
name = models.CharField(_('Subject'), max_length=255)
|
||||
created = models.DateTimeField(_('Created'), null=True)
|
||||
updated = models.DateTimeField(_('Updated'), null=True)
|
||||
user = models.ForeignKey(User, verbose_name=_('User'))
|
||||
views = models.IntegerField(_('Views count'), blank=True, default=0)
|
||||
sticky = models.BooleanField(_('Sticky'), blank=True, default=False)
|
||||
closed = models.BooleanField(_('Closed'), blank=True, default=False)
|
||||
subscribers = models.ManyToManyField(User, related_name='subscriptions', verbose_name=_('Subscribers'))
|
||||
post_count = models.IntegerField(_('Post count'), blank=True, default=0)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created']
|
||||
verbose_name = _('Topic')
|
||||
verbose_name_plural = _('Topics')
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def head(self):
|
||||
return self.posts.all().order_by('created').select_related()[0]
|
||||
|
||||
@property
|
||||
def last_post(self):
|
||||
return self.posts.all().order_by('-created').select_related()[0]
|
||||
|
||||
def reply_count(self):
|
||||
return self.post_count - 1
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('topic', args=[self.id])
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.id is None:
|
||||
self.created = datetime.now()
|
||||
super(Topic, self).save(*args, **kwargs)
|
||||
|
||||
def update_read(self, user):
|
||||
read, new = Read.objects.get_or_create(user=user, topic=self)
|
||||
if not new:
|
||||
read.time = datetime.now()
|
||||
read.save()
|
||||
|
||||
#def has_unreads(self, user):
|
||||
#try:
|
||||
#read = Read.objects.get(user=user, topic=self)
|
||||
#except Read.DoesNotExist:
|
||||
#return True
|
||||
#else:
|
||||
#return self.updated > read.time
|
||||
|
||||
|
||||
class Post(models.Model):
|
||||
topic = models.ForeignKey(Topic, related_name='posts', verbose_name=_('Topic'))
|
||||
user = models.ForeignKey(User, related_name='posts', verbose_name=_('User'))
|
||||
created = models.DateTimeField(_('Created'), blank=True)
|
||||
updated = models.DateTimeField(_('Updated'), blank=True, null=True)
|
||||
markup = models.CharField(_('Markup'), max_length=15, default=settings.FORUM_DEFAULT_MARKUP, choices=MARKUP_CHOICES)
|
||||
body = models.TextField(_('Message'))
|
||||
body_html = models.TextField(_('HTML version'))
|
||||
body_text = models.TextField(_('Text version'))
|
||||
user_ip = models.IPAddressField(_('User IP'), blank=True, default='')
|
||||
|
||||
|
||||
class Meta:
|
||||
ordering = ['created']
|
||||
verbose_name = _('Post')
|
||||
verbose_name_plural = _('Posts')
|
||||
|
||||
def summary(self):
|
||||
LIMIT = 50
|
||||
tail = len(self.body) > LIMIT and '...' or ''
|
||||
return self.body[:LIMIT] + tail
|
||||
|
||||
__unicode__ = summary
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.created is None:
|
||||
self.created = datetime.now()
|
||||
if self.markup == 'bbcode':
|
||||
self.body_html = mypostmarkup.markup(self.body, auto_urls=False)
|
||||
elif self.markup == 'markdown':
|
||||
self.body_html = unicode(Markdown(self.body, safe_mode='escape'))
|
||||
#self.body_html = markdown(self.body, 'safe')
|
||||
else:
|
||||
raise Exception('Invalid markup property: %s' % self.markup)
|
||||
self.body_text = strip_tags(self.body_html)
|
||||
self.body_html = urlize(self.body_html)
|
||||
|
||||
new = self.id is None
|
||||
|
||||
if new:
|
||||
self.topic.updated = datetime.now()
|
||||
self.topic.post_count += 1
|
||||
self.topic.save()
|
||||
self.topic.forum.updated = self.topic.updated
|
||||
self.topic.forum.post_count += 1
|
||||
self.topic.forum.save()
|
||||
|
||||
super(Post, self).save(*args, **kwargs)
|
||||
|
||||
if new:
|
||||
notify_subscribers(self)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('post', args=[self.id])
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
self_id = self.id
|
||||
head_post_id = self.topic.posts.order_by('created')[0].id
|
||||
super(Post, self).delete(*args, **kwargs)
|
||||
self.topic.forum.post_count -= 1
|
||||
self.topic.post_count -= 1
|
||||
if self_id == head_post_id:
|
||||
self.topic.delete()
|
||||
|
||||
class Reputation(models.Model):
|
||||
from_user = models.ForeignKey(User, related_name='reputations_from', verbose_name=_('From'))
|
||||
to_user = models.ForeignKey(User, related_name='reputations_to', verbose_name=_('To'))
|
||||
topic = models.ForeignKey(Topic, related_name='topic', verbose_name=_('Topic'))
|
||||
time = models.DateTimeField(_('Time'), blank=True)
|
||||
sign = models.IntegerField(_('Sign'), choices=SIGN_CHOICES, default=0)
|
||||
reason = models.TextField(_('Reason'), blank=True, default='', max_length='1000')
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Reputation')
|
||||
verbose_name_plural = _('Reputations')
|
||||
|
||||
def __unicode__(self):
|
||||
return u'T[%d], FU[%d], TU[%d]: %s' % (self.topic.id, self.from_user.id, self.to_user.id, unicode(self.time))
|
||||
|
||||
class Profile(models.Model):
|
||||
user = AutoOneToOneField(User, related_name='forum_profile', verbose_name=_('User'))
|
||||
#group = models.ForeignKey(Group, verbose_name=_('Group'), default='Member')
|
||||
status = models.CharField(_('Status'), max_length=30, blank=True, default='')
|
||||
site = models.URLField(_('Site'), verify_exists=False, blank=True, default='')
|
||||
jabber = models.CharField(_('Jabber'), max_length=80, blank=True, default='')
|
||||
icq = models.CharField(_('ICQ'), max_length=12, blank=True, default='')
|
||||
msn = models.CharField(_('MSN'), max_length=80, blank=True, default='')
|
||||
aim = models.CharField(_('AIM'), max_length=80, blank=True, default='')
|
||||
yahoo = models.CharField(_('Yahoo'), max_length=80, blank=True, default='')
|
||||
location = models.CharField(_('Location'), max_length=30, blank=True, default='')
|
||||
signature = models.TextField(_('Signature'), blank=True, default='', max_length=settings.FORUM_SIGNATURE_MAX_LENGTH)
|
||||
time_zone = models.FloatField(_('Time zone'), choices=TZ_CHOICES, default=float(settings.FORUM_DEFAULT_TIME_ZONE))
|
||||
language = models.CharField(_('Language'), max_length=3, blank=True, default='en', choices=LANGUAGE_CHOICES)
|
||||
avatar = ExtendedImageField(_('Avatar'), blank=True, default='', upload_to=settings.FORUM_AVATARS_UPLOAD_TO, width=settings.FORUM_AVATAR_WIDTH, height=settings.FORUM_AVATAR_HEIGHT)
|
||||
theme = models.CharField(_('Theme'), choices=THEME_CHOICES, max_length=80, default='')
|
||||
show_avatar = models.BooleanField(_('Show avatar'), blank=True, default=True)
|
||||
show_signatures = models.BooleanField(_('Show signatures'), blank=True, default=True)
|
||||
privacy_permission = models.IntegerField(_('Privacy permission'), choices=PRIVACY_CHOICES, default=1)
|
||||
markup = models.CharField(_('Default markup'), max_length=15, default=settings.FORUM_DEFAULT_MARKUP, choices=MARKUP_CHOICES)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Profile')
|
||||
verbose_name_plural = _('Profiles')
|
||||
|
||||
def last_post(self):
|
||||
posts = Post.objects.filter(user=self.user).order_by('-created').select_related()
|
||||
if posts.count():
|
||||
return posts[0].created
|
||||
else:
|
||||
return None
|
||||
|
||||
def reply_total(self):
|
||||
total = 0
|
||||
for reputation in Reputation.objects.filter(to_user=self.user).select_related():
|
||||
total += reputation.sign
|
||||
return total
|
||||
|
||||
def reply_count_minus(self):
|
||||
return Reputation.objects.filter(to_user=self.user, sign=-1).select_related().count()
|
||||
|
||||
def reply_count_plus(self):
|
||||
return Reputation.objects.filter(to_user=self.user, sign=1).select_related().count()
|
||||
|
||||
class Read(models.Model):
|
||||
"""
|
||||
For each topic that user has entered the time
|
||||
is logged to this model.
|
||||
"""
|
||||
|
||||
user = models.ForeignKey(User, verbose_name=_('User'))
|
||||
topic = models.ForeignKey(Topic, verbose_name=_('Topic'))
|
||||
time = models.DateTimeField(_('Time'), blank=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ['user', 'topic']
|
||||
verbose_name = _('Read')
|
||||
verbose_name_plural = _('Reads')
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.time is None:
|
||||
self.time = datetime.now()
|
||||
super(Read, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
def __unicode__(self):
|
||||
return u'T[%d], U[%d]: %s' % (self.topic.id, self.user.id, unicode(self.time))
|
||||
|
||||
class Report(models.Model):
|
||||
reported_by = models.ForeignKey(User, related_name='reported_by', verbose_name=_('Reported by'))
|
||||
post = models.ForeignKey(Post, verbose_name=_('Post'))
|
||||
zapped = models.BooleanField(_('Zapped'), blank=True, default=False)
|
||||
zapped_by = models.ForeignKey(User, related_name='zapped_by', blank=True, null=True, verbose_name=_('Zapped by'))
|
||||
created = models.DateTimeField(_('Created'), blank=True)
|
||||
reason = models.TextField(_('Reason'), blank=True, default='', max_length='1000')
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Report')
|
||||
verbose_name_plural = _('Reports')
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s %s' % (self.reported_by ,self.zapped)
|
||||
|
||||
class PrivateMessage(models.Model):
|
||||
|
||||
dst_user = models.ForeignKey(User, verbose_name=_('Recipient'), related_name='dst_users')
|
||||
src_user = models.ForeignKey(User, verbose_name=_('Author'), related_name='src_users')
|
||||
read = models.BooleanField(_('Read'), blank=True, default=False)
|
||||
created = models.DateTimeField(_('Created'), blank=True)
|
||||
markup = models.CharField(_('Markup'), max_length=15, default=settings.FORUM_DEFAULT_MARKUP, choices=MARKUP_CHOICES)
|
||||
subject = models.CharField(_('Subject'), max_length=255)
|
||||
body = models.TextField(_('Message'))
|
||||
body_html = models.TextField(_('HTML version'))
|
||||
body_text = models.TextField(_('Text version'))
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created']
|
||||
verbose_name = _('Private message')
|
||||
verbose_name_plural = _('Private messages')
|
||||
|
||||
# TODO: summary and part of the save methid is the same as in the Post model
|
||||
# move to common functions
|
||||
def summary(self):
|
||||
LIMIT = 50
|
||||
tail = len(self.body) > LIMIT and '...' or ''
|
||||
return self.body[:LIMIT] + tail
|
||||
|
||||
def __unicode__(self):
|
||||
return self.subject
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.created is None:
|
||||
self.created = datetime.now()
|
||||
if self.markup == 'bbcode':
|
||||
self.body_html = mypostmarkup.markup(self.body, auto_urls=False)
|
||||
elif self.markup == 'markdown':
|
||||
self.body_html = unicode(Markdown(self.body, safe_mode='escape'))
|
||||
#self.body_html = markdown(self.body, 'safe')
|
||||
else:
|
||||
raise Exception('Invalid markup property: %s' % self.markup)
|
||||
self.body_text = strip_tags(self.body_html)
|
||||
self.body_html = urlize(self.body_html)
|
||||
|
||||
new = self.id is None
|
||||
super(PrivateMessage, self).save(*args, **kwargs)
|
||||
# TODO: make email notifications
|
||||
#if new:
|
||||
#notify_subscribers(self)
|
40
apps/forum/subscription.py
Normal file
40
apps/forum/subscription.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
from django.core.mail import EmailMultiAlternatives
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
def notify_subscribers(post):
|
||||
from apps.forum.models import Post
|
||||
|
||||
topic = post.topic
|
||||
if post != topic.head:
|
||||
for user in topic.subscribers.all():
|
||||
if user != post.user:
|
||||
subject = u'RE: %s' % topic.name
|
||||
from_email = settings.DEFAULT_FROM_EMAIL
|
||||
to_email = user.email
|
||||
text_content = text_version(post)
|
||||
#html_content = html_version(post)
|
||||
msg = EmailMultiAlternatives(subject, text_content, from_email, [to_email])
|
||||
#msg.attach_alternative(html_content, "text/html")
|
||||
msg.send(fail_silently=True)
|
||||
|
||||
|
||||
TEXT_TEMPLATE = _(u"""New reply from %(username)s to topic that you have subscribed on.
|
||||
---
|
||||
%(message)s
|
||||
---
|
||||
See topic: %(post_url)s
|
||||
Unsubscribe %(unsubscribe_url)s""")
|
||||
|
||||
def text_version(post):
|
||||
data = TEXT_TEMPLATE % {
|
||||
'username': post.user.username,
|
||||
'message': post.body_text,
|
||||
'post_url': absolute_url(post.get_absolute_url()),
|
||||
'unsubscribe_url': absolute_url(reverse('forum_delete_subscription', args=[post.topic.id])),
|
||||
}
|
||||
return data
|
||||
|
||||
def absolute_url(uri):
|
||||
return 'http://%s%s' % (settings.FORUM_HOST, uri)
|
9
apps/forum/templates/forum/.tmp_ban.html~
Normal file
9
apps/forum/templates/forum/.tmp_ban.html~
Normal file
|
@ -0,0 +1,9 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
|
||||
<title>Untitled Document</title>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
70
apps/forum/templates/forum/.tmp_profile_personality.html~
Normal file
70
apps/forum/templates/forum/.tmp_profile_personality.html~
Normal file
|
@ -0,0 +1,70 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div id="profile" class="block2col">
|
||||
{% include 'forum/profile_menu.html' %}
|
||||
<div class="blockform">
|
||||
<h2><span>{{ profile.username }} - Индивидуальный</span></h2>
|
||||
<div class="box">
|
||||
<form id="profile4" method="post">
|
||||
<div class="inform">
|
||||
<fieldset id="profileavatar">
|
||||
<legend>Настройка отображения аватара</legend>
|
||||
<div class="infldset">
|
||||
{% if profile.forum_profile.avatar %}
|
||||
<img src="{{ profile.forum_profile.avatar.url }}" />
|
||||
{% endif %}
|
||||
<p>Аватар
|
||||
- это маленькое изображение, которое будет отображаться под Вашим
|
||||
именем в сообщениях на форуме. Вы можете загрузить аватар, нажав на
|
||||
ссылку ниже. Для того чтобы аватар был виден в ваших сообщениях,
|
||||
отметьте галочкой поле 'Использовать аватар'.</p>
|
||||
<div class="rbox">
|
||||
<label><input name="form[use_avatar]" value="1" checked="checked" type="checkbox">Использовать аватар.<br></label>
|
||||
|
||||
</div>
|
||||
{% if profile.forum_profile.avatar %}
|
||||
<p class="clearb"><a href="{% url forum_profile profile.username %}?action=upload_avatar">Изменить аватар</a>
|
||||
<a href="{% url forum_profile profile.username %}?action=delete_avatar">Удалить аватар</a></p>
|
||||
{% else %}
|
||||
<p class="clearb"><a href="{% url forum_profile profile.username %}?action=upload_avatar">Загрузить аватар</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>Составте вашу подпись</legend>
|
||||
|
||||
<div class="infldset">
|
||||
<p>Подпись
|
||||
- это небольшая приписка, прилагаемая к вашим сообщениям. Это может
|
||||
быть все, что вам нравится. Например, ваша любимая цитата. В подписи
|
||||
можно использовать встроенные теги BBCode и/или HTML, если это
|
||||
разрешено администратором. Что разрешено, можно увидеть слева, при
|
||||
редактировании подписи.</p>
|
||||
<div class="txtarea">
|
||||
<label>Макс. символов: 400 / Макс. строк: 4<br>
|
||||
{{ form.signature }}
|
||||
<br></label>
|
||||
</div>
|
||||
{% if profile.forum_profile.signature %}
|
||||
<p>{{ profile.forum_profile.signature }}</p>
|
||||
{% else %}
|
||||
<p>Нет подписи.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<p><input name="update" value="Отправить" type="submit">После обновления профиля, вы будете перенаправлены назад на эту страницу.</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
{% endblock %}
|
54
apps/forum/templates/forum/.tmp_profile_privacy.html~
Normal file
54
apps/forum/templates/forum/.tmp_profile_privacy.html~
Normal file
|
@ -0,0 +1,54 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div id="profile" class="block2col">
|
||||
{% include 'forum/profile_menu.html' %}
|
||||
<div class="blockform">
|
||||
<h2><span>{{ profile.username }} - Личный</span></h2>
|
||||
<div class="box">
|
||||
<form id="profile6" method="post">
|
||||
<div class="inform">
|
||||
|
||||
<fieldset>
|
||||
<legend>Выберите свои личные настройки</legend>
|
||||
<div class="infldset">
|
||||
<input name="form_sent" value="1" type="hidden">
|
||||
<p>Выберете,
|
||||
каким образом вы хотите, чтобы ваш e-mail адрес отображался на форуме,
|
||||
и хотите ли вы, чтобы другие пользователи имели возможность слать вам
|
||||
e-mail сообщения через форум или нет.</p>
|
||||
<div class="rbox">
|
||||
<label><input name="form[email_setting]" value="0" type="radio">Показывать ваш e-mail адрес.<br></label>
|
||||
|
||||
<label><input name="form[email_setting]" value="1" checked="checked" type="radio">Скрыть ваш e-mail адрес, но разрешить слать вам сообщения через форум.<br></label>
|
||||
<label><input name="form[email_setting]" value="2" type="radio">Скрыть ваш e-mail адрес и запретить слать вам сообщения через форум.<br></label>
|
||||
</div>
|
||||
<p>Включение
|
||||
этой опции даёт возможность форуму "запоминать" вас между визитами (имя
|
||||
и пароль сохраняются в куках вашего броузера). В этом случае, вам не
|
||||
нужно будет каждый раз вводить ваше имя и пароль при заходе на форум.
|
||||
Вход будет произведён автоматически. Рекомендуется.</p>
|
||||
<div class="rbox">
|
||||
<label><input name="form[save_pass]" value="1" checked="checked" type="checkbox">Запомнить имя пользователя и пароль между визитами.<br></label>
|
||||
</div>
|
||||
|
||||
<p>При включении этой опции, в тело уведомлений о новых сообщениях на e-mail, будет включаться текст самих сообщений.</p>
|
||||
<div class="rbox">
|
||||
<label><input name="form[notify_with_post]" value="1" type="checkbox">Включать сообщение целиком, при подписке об уведомлениях о новых соообщениях на форуме на e-mail.<br></label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<p><input name="update" value="Отправить" type="submit">После обновления профиля, вы будете перенаправлены назад на эту страницу.</p>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
{% endblock %}
|
66
apps/forum/templates/forum/.tmp_search_result.html~
Normal file
66
apps/forum/templates/forum/.tmp_search_result.html~
Normal file
|
@ -0,0 +1,66 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="linkst">
|
||||
<div class="inbox">
|
||||
<p class="pagelink">Страниц: <strong>1</strong> <a href="http://python.su/forum/search.php?search_id=458844699&p=2">2</a></p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="vf" class="blocktable">
|
||||
<h2><span>Результаты поиска</span></h2>
|
||||
<div class="box">
|
||||
<div class="inbox">
|
||||
<table cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
<th class="tcl" scope="col">Тема</th>
|
||||
<th class="tc2" scope="col">Форум</th>
|
||||
<th class="tc3" scope="col">Ответов</th>
|
||||
<th class="tcr" scope="col">Последнее сообщение</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<tr class="inew">
|
||||
<td class="tcl">
|
||||
<div class="intd">
|
||||
<div class="icon inew"><div class="nosize"><!-- --> Есть новые сообщения</div></div>
|
||||
<div class="tclcon">
|
||||
<strong><a href="http://python.su/forum/viewtopic.php?id=3282">Отправка ICQ-сообщений из loop'a</a> <span class="byuser">оставил andr0s</span></strong> <span class="newtext">[ <a href="http://python.su/forum/viewtopic.php?id=3282&action=new" title="Перейти к первому новому сообщению в этом топике.">Новые сообщения</a> ]</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="tc2"><a href="http://python.su/forum/viewforum.php?id=25">Network</a></td>
|
||||
<td class="tc3">0</td>
|
||||
<td class="tcr"><a href="http://python.su/forum/viewtopic.php?pid=20665#p20665">Сегодня 15:55:00</a> оставил andr0s</td>
|
||||
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="linksb">
|
||||
<div class="inbox">
|
||||
<p class="pagelink">Страниц: <strong>1</strong> <a href="http://python.su/forum/search.php?search_id=458844699&p=2">2</a></p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
<dl id="searchlinks" class="conl">
|
||||
<dt><strong>Search links</strong></dt>
|
||||
<dd><a href="search.php?action=show_24h">Показать последние сообщения</a></dd>
|
||||
<dd><a href="search.php?action=show_unanswered">Показать сообщения, не имеющие ответов</a></dd>
|
||||
<dd><a href="search.php?action=show_subscriptions">Показать сообщения на которые Вы подписаны</a></dd>
|
||||
<dd><a href="search.php?action=show_user&user_id=2">Показать Ваши сообщения</a></dd>
|
||||
</dl>
|
||||
{% endblock %}
|
66
apps/forum/templates/forum/add_post.html
Normal file
66
apps/forum/templates/forum/add_post.html
Normal file
|
@ -0,0 +1,66 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="linkst">
|
||||
<div class="inbox">
|
||||
{% if forum %}
|
||||
<ul class="start"><li><a href="{% url index %}">{% trans "Root" %}</a> </li><li>» {% link forum %}</li></ul>
|
||||
{% else %}
|
||||
<ul><li><a href="{% url index %}">{% trans "Root" %}</a> </li><li>» {% link topic.forum %}</li><li>» {{ topic }}</li></ul>
|
||||
{% endif %}
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="blockform">
|
||||
<h2><span>{% if forum %}{% trans "New topic" %}{% else %}{% trans "New reply" %}{% endif %}</span></h2>
|
||||
<div class="box">
|
||||
<form id="post" action="{% if forum %}{% url add_topic forum.id %}{% else %}{% url add_post topic.id %}{% endif %}" method="post">
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Write your message and submit" %}</legend>
|
||||
<div class="infldset txtarea">
|
||||
{% if forum %}
|
||||
<label><strong>{% trans "Subject" %}</strong><br />{{ form.name }}<br /></label>
|
||||
{% endif %}
|
||||
<label><strong>{% trans "Message" %}</strong><br />{{ form.body }}<br /></label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<p><input type="submit" value="{% trans "Submit" %}" /><a href="javascript:history.go(-1)">{% trans "Go back" %}</a></p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if not forum %}
|
||||
<div id="postreview" class="blockpost">
|
||||
|
||||
<h2><span>{% trans "Topic review (newest first)" %}</span></h2>
|
||||
{% for post in posts reversed %}
|
||||
<div class="box rowodd">
|
||||
<div class="inbox">
|
||||
<div class="postleft">
|
||||
<dl>
|
||||
<dt><strong><a href="javascript:pasteN('{{ post.user }}');">{{ post.user }}</a></strong></dt>
|
||||
<dd>{% forum_time post.created %}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="postright">
|
||||
<div class="postmsg">
|
||||
{{ post.body_html|safe }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
<div class="postfootright"><ul><li class="postquote"><a onmouseover="copyQ('{{ post.user }}');" href="javascript:pasteQ();">{% trans "Quote" %}</a></li></ul></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
61
apps/forum/templates/forum/base.html
Normal file
61
apps/forum/templates/forum/base.html
Normal file
|
@ -0,0 +1,61 @@
|
|||
{% load i18n %}
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru" lang="ru">
|
||||
<head>
|
||||
<title>DjangoBB demo</title>
|
||||
<meta name="description" content="DjangoBB demo" />
|
||||
<meta name="keywords" content="DjangoBB" />
|
||||
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
{% if request.user.is_authenticated and request.user.forum_profile.theme %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}forum/themes/{{ request.user.forum_profile.theme }}/style.css" />
|
||||
{% else %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}forum/themes/phpBB/style.css" />
|
||||
{% endif %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}forum/js/markitup/skins/markitup/style.css" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}forum/js/markitup/sets/bbcode/style.css" />
|
||||
|
||||
<link rel="alternate" type="application/atom+xml" href="{% url forum_feed "posts" %}" title="{% trans "Latest posts on forum" %}" />
|
||||
<link rel="alternate" type="application/atom+xml" href="{% url forum_feed "topics" %}" title="{% trans "Latest topics on forum" %}" />
|
||||
|
||||
<!--<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />-->
|
||||
|
||||
<script type="text/javascript" src="{{ MEDIA_URL }}forum/js/jquery.pack.js"></script>
|
||||
<script type="text/javascript" src="{{ MEDIA_URL }}forum/js/board.js"></script>
|
||||
<script type="text/javascript" src="{{ MEDIA_URL }}forum/js/markitup/jquery.markitup.js"></script>
|
||||
<script type="text/javascript" src="{{ MEDIA_URL }}forum/js/markitup/sets/bbcode/set.js"></script>
|
||||
|
||||
<script type="text/javascript" >
|
||||
$(document).ready(function() {
|
||||
$(".bbcode").markItUp(mySettings);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<!-- Highlightjs goodies -->
|
||||
<script type="text/javascript" src="{{ MEDIA_URL }}forum/js/highlight.js"></script>
|
||||
<script type="text/javascript">
|
||||
hljs.initHighlightingOnLoad();
|
||||
</script>
|
||||
<link type="text/css" rel="stylesheet" href="{{ MEDIA_URL }}forum/css/hljs_styles/phpbb_blue.css" />
|
||||
|
||||
{% block extra_meta %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% include 'forum/head.html' %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
<div id="brdfooter" class="block">
|
||||
<h2><span>{% trans "Board footer" %}</span></h2>
|
||||
<div class="box">
|
||||
<div class="inbox">
|
||||
{% block controls %}
|
||||
{% endblock %}
|
||||
<p class="conr">Powered by <a href="http://www.djangobb.org/">DjangoBB</a></p>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
61
apps/forum/templates/forum/delete_posts.html
Normal file
61
apps/forum/templates/forum/delete_posts.html
Normal file
|
@ -0,0 +1,61 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="linkst">
|
||||
<div class="inbox">
|
||||
<p class="pagelink conl">{% pagination %}</p>
|
||||
<ul><li><a href="{% url index %}">{% trans "Root" %} </a></li><li>» {% link topic.forum %} </li><li>» {{ topic }}
|
||||
<a href="{% url forum_feed "topic" %}{{ topic.id }}"><img src="{{ MEDIA_URL }}/forum/img/feed-icon-small.png" alt="[RSS Feed]" title="[RSS Feed]" style="vertical-align:middle;" /></a>
|
||||
</li></ul>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
|
||||
{% for post in posts %}
|
||||
<div id="p{{ post.id }}" class="blockpost roweven firstpost">
|
||||
<a name="post-{{ post.id }}"></a>
|
||||
<h2><span><span class="conr">#{{ forloop.counter }} </span><a href="{{ post.get_absolute_url }}">{% forum_time post.created %}</a></span></h2>
|
||||
<div class="box">
|
||||
<div class="inbox">
|
||||
<div class="postleft">
|
||||
|
||||
<dl>
|
||||
<dt><strong><a href="{% url forum_profile post.user %}">{{ post.user }}</a></strong></dt>
|
||||
<dd class="usertitle"><strong>{{ post.user.forum_profile.status }}</strong></dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="postright">
|
||||
<h3>{{ post.topic.name }}</h3>
|
||||
|
||||
<div class="postmsg">
|
||||
{{ post.body_html|safe }}
|
||||
{% if post.updated %}
|
||||
<p class="postedit"><em>{% trans "Edited" %} {{ post.user }} ({% forum_time post.updated %})</em></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if not forloop.first %}
|
||||
<p class="multidelete"><label><strong>{% trans "Select" %}</strong> <input type="checkbox" name="post[{{ post.id }}]" value="1" /></label></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="postlinksb">
|
||||
<div class="inbox">
|
||||
<p class="pagelink conl">{% pagination %}</p>
|
||||
<p class="conr"><input type="submit" value="{% trans "Delete" %}" /></p>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
37
apps/forum/templates/forum/edit_post.html
Normal file
37
apps/forum/templates/forum/edit_post.html
Normal file
|
@ -0,0 +1,37 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="linkst">
|
||||
<div class="inbox">
|
||||
<ul><li><a href="{% url index %}">{% trans "Root" %}</a> </li><li>» {% link post.topic.forum %}</li><li>» {{ post.topic }}</li></ul>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="blockform">
|
||||
<h2>{% trans "Edit post" %}</h2>
|
||||
|
||||
<div class="box">
|
||||
<form method="post">
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Edit the post and submit changes" %}</legend>
|
||||
<div class="infldset txtarea">
|
||||
{% ifequal post.id post.topic.head.id %}
|
||||
<label>{% trans "Subject" %}<br />
|
||||
{{ form.name }}<br /></label>
|
||||
{% endifequal %}
|
||||
<label>{% trans "Message" %}<br />
|
||||
{{ form.body }}
|
||||
<br /></label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<p><input type="submit" value="{% trans "Save" %}" /><a href="javascript:history.go(-1)">{% trans "Go back" %}</a></p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
1
apps/forum/templates/forum/feeds/posts_description.html
Normal file
1
apps/forum/templates/forum/feeds/posts_description.html
Normal file
|
@ -0,0 +1 @@
|
|||
{{ obj.body_html|safe }}
|
1
apps/forum/templates/forum/feeds/posts_title.html
Normal file
1
apps/forum/templates/forum/feeds/posts_title.html
Normal file
|
@ -0,0 +1 @@
|
|||
{{ obj.topic.forum.category|safe }} :: {{ obj.topic.forum|safe }} :: {{ obj.topic|safe }}
|
1
apps/forum/templates/forum/feeds/topics_description.html
Normal file
1
apps/forum/templates/forum/feeds/topics_description.html
Normal file
|
@ -0,0 +1 @@
|
|||
{{ obj.head.body_html|safe }}
|
1
apps/forum/templates/forum/feeds/topics_title.html
Normal file
1
apps/forum/templates/forum/feeds/topics_title.html
Normal file
|
@ -0,0 +1 @@
|
|||
{{ obj.forum.category|safe }} :: {{ obj.forum|safe }} :: {{ obj|safe }}
|
117
apps/forum/templates/forum/forum.html
Normal file
117
apps/forum/templates/forum/forum.html
Normal file
|
@ -0,0 +1,117 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="linkst">
|
||||
<div class="inbox">
|
||||
<p class="pagelink conl">{% pagination %}</p>
|
||||
<p class="postlink conr"><a href="{% url add_topic forum.id %}">{% trans "New topic" %}</a></p>
|
||||
<ul><li><a href="{% url index %}">{% trans "Root" %}</a> </li><li>» {{ forum }}</li></ul>
|
||||
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="vf" class="blocktable">
|
||||
<h2>
|
||||
<a href="{% url forum_feed "forum" %}{{ forum.id }}"><img src="{{ MEDIA_URL }}/forum/img/feed-icon-small.png" alt="[RSS Feed]" title="[RSS Feed]" style="float:right;" /></a>
|
||||
<b><span>{{ forum }}</span></b>
|
||||
</h2>
|
||||
<div class="box">
|
||||
|
||||
<div class="inbox">
|
||||
<table cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="tcl" scope="col">{% trans "Topic" %}</th>
|
||||
<th class="tc2" scope="col">{% trans "Replies" %}</th>
|
||||
<th class="tc3" scope="col">{% trans "Views" %}</th>
|
||||
|
||||
<th class="tcr" scope="col">{% trans "Last post" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if sticky_topics or topics %}
|
||||
{% for topic in sticky_topics|forum_unreads:user %}
|
||||
<tr class="isticky">
|
||||
<td class="tcl">
|
||||
<div class="intd">
|
||||
<div class="icon"><div class="nosize"><!-- --></div></div>
|
||||
<div class="tclcon">
|
||||
{% if topic|has_unreads:user %}
|
||||
<strong>{% link topic %} <span class="byuser">{% trans "by" %} {{ topic.user }}</span></strong>
|
||||
{% else %}
|
||||
{% link topic %} <span class="byuser">{% trans "by" %} {{ topic.user }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="tc2">{{ topic.post_count }}</td>
|
||||
<td class="tc3">{{ topic.views }}</td>
|
||||
<td class="tcr"><a href="{{ topic.get_absolute_url }}">{% forum_time topic.updated %}</a> <span class="byuser">{% trans "by" %} {{ topic.last_post.user }}</span></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
{% for topic in topics|forum_unreads:user %}
|
||||
<tr {% if topic|has_unreads:user %}class="inew"{% endif %} {% if topic.closed %}class="iclosed"{% endif %}>
|
||||
<td class="tcl">
|
||||
<div class="intd">
|
||||
<div class="icon"><div class="nosize"><!-- --></div></div>
|
||||
<div class="tclcon">
|
||||
{% if topic|has_unreads:user %}
|
||||
<strong>{% link topic %} <span class="byuser">{% trans "by" %} {{ topic.user }}</span></strong>
|
||||
{% else %}
|
||||
{% link topic %} <span class="byuser">{% trans "by" %} {{ topic.user }}</span>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="tc2">{{ topic.reply_count }}</td>
|
||||
<td class="tc3">{{ topic.views }}</td>
|
||||
<td class="tcr"><a href="{{ topic.get_absolute_url }}">{% forum_time topic.updated %}</a> <span class="byuser">{% trans "by" %} {{ topic.last_post.user }}</span></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr><td class="puncon1" colspan="4">{% trans "Forum is empty." %}</td></tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="linkst">
|
||||
<div class="inbox">
|
||||
<p class="pagelink conl">{% pagination %}</p>
|
||||
<p class="postlink conr"><a href="{% url add_topic forum.id %}">{% trans "New topic" %}</a></p>
|
||||
<ul><li><a href="{% url index %}">{% trans "Root" %}</a> </li><li>» {{ forum }}</li></ul>
|
||||
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
<div class="conl">
|
||||
<form id="qjump" method="get" action="forum">
|
||||
<div><label>{% trans "Jump to" %}
|
||||
<br />
|
||||
<select name="id" id="forum_id" onchange="window.location=('/forum/'+this.options[this.selectedIndex].value)">
|
||||
{% for category in categories %}
|
||||
<optgroup label="{{ category }}">
|
||||
{% for forum in category.forums.all %}
|
||||
<option value="{{ forum.id }}">{{ forum }}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="button" onclick="window.location=('/forum/'+getElementById('forum_id').value)" value=" {% trans "Go" %} " accesskey="g" />
|
||||
</label></div>
|
||||
|
||||
</form>
|
||||
{% if moderator %}
|
||||
<p id="modcontrols"><a href="{% url moderate forum.id %}">{% trans "Moderate forum" %}</a></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
36
apps/forum/templates/forum/forum_row.html
Normal file
36
apps/forum/templates/forum/forum_row.html
Normal file
|
@ -0,0 +1,36 @@
|
|||
{% load forum_extras %}
|
||||
|
||||
{% if forum.last_post.topic %}
|
||||
<tr {% if forum.last_post.topic|has_unreads:user %}class="inew"{% endif %}>
|
||||
{% else %}
|
||||
<tr>
|
||||
{% endif %}
|
||||
<td class="tcl">
|
||||
<div class="intd">
|
||||
<div class="icon">
|
||||
<div class="nosize"><!-- --></div>
|
||||
</div>
|
||||
<div class="tclcon">
|
||||
<h3>{% link forum %} <a href="{% url forum_feed "forum" %}{{ forum.id }}"><img src="{{ MEDIA_URL }}/forum/img/feed-icon-small.png" alt="[RSS Feed]" title="[RSS Feed]" style="float:right;" /></a></h3>
|
||||
{{ forum.description|safe }}
|
||||
|
||||
<p>
|
||||
{% if forum.moderators.count %}
|
||||
{% include 'forum/moderators.html' %}
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="tc2">{{ forum.topic_count }}</td>
|
||||
<td class="tc3">{{ forum.post_count }}</td>
|
||||
<td class="tcr">
|
||||
{% if forum.updated %}
|
||||
{% if forum.last_post.topic %}
|
||||
<a href="{{ forum.last_post.get_absolute_url }}">{% forum_time forum.last_post.created %}</a>
|
||||
<span class="byuser">оставил {{ forum.last_post.user }}</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
67
apps/forum/templates/forum/head.html
Normal file
67
apps/forum/templates/forum/head.html
Normal file
|
@ -0,0 +1,67 @@
|
|||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
<div id="punwrap">
|
||||
<div id="punindex" class="pun">
|
||||
|
||||
<div id="brdheader" class="block">
|
||||
<div class="box">
|
||||
|
||||
<div id="brdtitle" class="inbox">
|
||||
<h1><span><a href="{% url index %}">{{ "FORUM_HEADER"|forum_setting }}</a></span></h1>
|
||||
<p><span>{{ "FORUM_TAGLINE"|forum_setting|safe }}</span></p>
|
||||
</div>
|
||||
<div id="brdmenu" class="inbox">
|
||||
<ul>
|
||||
<li id="navindex"><a href="{% url index %}">{% trans "Index" %}</a></li>
|
||||
<li id="navuserlist"><a href="{% url forum_users %}">{% trans "User list" %}</a></li>
|
||||
<li id="navsearch"><a href="{% url search %}">{% trans "Search" %}</a></li>
|
||||
{% if user.is_superuser %}
|
||||
<li id="navadmin"><a href="/admin">{% trans "Administration" %}</a></li>
|
||||
{% endif %}
|
||||
{% if user.is_authenticated %}
|
||||
<li id="navprofile"><a href="{% url forum_profile request.user %}">{% trans "Profile" %}</a></li>
|
||||
<li id="navpm"><a href="{% url forum_pm_inbox %}">{% trans "PM" %}</a></li>
|
||||
<li id="navlogout"><a href="{% url auth_logout %}">{% trans "Log out" %}</a></li>
|
||||
{% else %}
|
||||
<li><a href="{% url auth_login %}">{% trans "Log in" %}</a></li>
|
||||
<li><a href="{% url registration_register %}">{% trans "Sign up" %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
<div id="brdwelcome" class="inbox">
|
||||
<ul class="conl">
|
||||
{% if user.is_authenticated %}
|
||||
<li>{% trans "Logged in as" %} <strong>{{ user.username }}</strong></li>
|
||||
<li>{% trans "Last visit:" %} {% forum_time user.last_login %}</li>
|
||||
{% if request.user|pm_unreads %}
|
||||
<ul><li class="pmlink"><strong><a href="{% url forum_pm_inbox %}">{% blocktrans with request.user|pm_unreads as new_msg %} There are new messages ({{ new_msg }}){% endblocktrans %}</a></strong></li></ul>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<li>{% trans "You are not logged in." %}</li>
|
||||
{% endif %}
|
||||
{% if user.is_superuser and reports %}
|
||||
<li class="reportlink"><strong><a href="/admin">{% trans "There are new reports" %} ({% new_reports %})</a></strong></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% if user.is_authenticated %}
|
||||
<ul class="conr">
|
||||
<li><a href="{% url search %}?action=show_new">{% trans "Show new posts since last visit" %}</a></li>
|
||||
<li><a href="{% url misc %}?action=markread">{% trans "Mark all topics as read" %}</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if "FORUM_NOTICE"|forum_setting %}
|
||||
<div id="announce" class="block">
|
||||
<h2><span>{% trans "Notice" %}</span></h2>
|
||||
<div class="box">
|
||||
<div class="inbox">
|
||||
<div>{{ "FORUM_NOTICE"|forum_setting }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
79
apps/forum/templates/forum/index.html
Normal file
79
apps/forum/templates/forum/index.html
Normal file
|
@ -0,0 +1,79 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div id="idx1" class="blocktable">
|
||||
{% for iter in cats %}
|
||||
<h2>
|
||||
<a href="{% url forum_feed "category" %}{{ iter.cat.id }}"><img src="{{ MEDIA_URL }}/forum/img/feed-icon-small.png" alt="[RSS Feed]" title="[RSS Feed]" style="float:right;" /></a>
|
||||
<span>{{ iter.cat }}</span>
|
||||
</h2>
|
||||
<div class="box">
|
||||
<div class="inbox">
|
||||
<table cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="tcl" scope="col">{% trans "Forum" %}</th>
|
||||
<th class="tc2" scope="col">{% trans "Topics" %}</th>
|
||||
<th class="tc3" scope="col">{% trans "Posts" %}</th>
|
||||
<th class="tcr" scope="col">{% trans "Last post" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for forum in iter.forums %}
|
||||
{% include 'forum/forum_row.html' %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div id="brdstats" class="block">
|
||||
<h2><span>{% trans "Board information" %}</span></h2>
|
||||
<div class="box">
|
||||
|
||||
<div class="inbox">
|
||||
<dl class="conr">
|
||||
<dt><strong>{% trans "Board statistics" %}</strong></dt>
|
||||
<dd>{% trans "Total number of registered users:" %} <strong>{{ users }}</strong></dd>
|
||||
<dd>{% trans "Total number of topics:" %} <strong>{{ topics }}</strong></dd>
|
||||
<dd>{% trans "Total number of posts:" %} <strong>{{ posts }}</strong></dd>
|
||||
|
||||
</dl>
|
||||
<dl class="conl">
|
||||
<dt><strong>{% trans "User information" %}</strong></dt>
|
||||
<dd>{% trans "Newest registered user:" %} {{ last_user|profile_link }}</dd>
|
||||
<dd>{% trans "Registered users online:" %} <strong>{{ online_count }}</strong></dd>
|
||||
<dd>{% trans "Guests online:" %} <strong>{{ guest_count }}</strong></dd>
|
||||
|
||||
</dl>
|
||||
<dl id="onlinelist" class= "clearb">
|
||||
{% if online_count %}
|
||||
<dt><strong>{% trans "Online:" %} </strong></dt>
|
||||
{% for online in users_online %}
|
||||
<dd>{{ online|profile_link }}</dd>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
<dl id="searchlinks" class="conl">
|
||||
<dt><strong>{% trans "Search links" %}</strong></dt>
|
||||
<dd><a href="{% url search %}?action=show_24h">{% trans "Show recent posts" %}</a></dd>
|
||||
<dd><a href="{% url search %}?action=show_unanswered">{% trans "Show unanswered posts" %}</a></dd>
|
||||
{% if user.is_authenticated %}
|
||||
<dd><a href="{% url search %}?action=show_subscriptions">{% trans "Show your subscribed topics" %}</a></dd>
|
||||
<dd><a href="{% url search %}?action=show_user&user_id={{ request.user.id }}">{% trans "Show your posts" %}</a></dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
{% endblock %}
|
||||
|
32
apps/forum/templates/forum/mail_to.html
Normal file
32
apps/forum/templates/forum/mail_to.html
Normal file
|
@ -0,0 +1,32 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="blockform">
|
||||
<h2><span>{% trans "Send e-mail to" %} {{ user }}</span></h2>
|
||||
<div class="box">
|
||||
<form id="email" method="post">
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Write and submit your e-mail message" %}</legend>
|
||||
|
||||
<div class="infldset txtarea">
|
||||
<label><strong>{{ form.subject.label }}</strong><br />
|
||||
{{ form.subject }}<br /></label>
|
||||
<label><strong>{{ form.body.label }}</strong><br />
|
||||
{{ form.body }}<br /></label>
|
||||
|
||||
<p>{% trans "Please note that by using this form, your e-mail address will be disclosed to the recipient." %}</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<p><input type="submit" name="submit" value="{% trans "Submit" %}" tabindex="3" accesskey="s" /><a href="javascript:history.go(-1)">{% trans "Go back" %}</a></p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
{% endblock %}
|
100
apps/forum/templates/forum/moderate.html
Normal file
100
apps/forum/templates/forum/moderate.html
Normal file
|
@ -0,0 +1,100 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="linkst">
|
||||
<div class="inbox">
|
||||
<p class="pagelink conl">{% pagination %}</p>
|
||||
<ul><li><a href="{% url index %}">{% trans "Root" %}</a> </li><li>» {{ forum }}</li></ul>
|
||||
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
<div id="vf" class="blocktable">
|
||||
<h2><span>{{ forum }}</span></h2>
|
||||
<div class="box">
|
||||
<div class="inbox">
|
||||
<table cellspacing="0">
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="tcl" scope="col">{% trans "Topic" %}</th>
|
||||
<th class="tc2" scope="col">{% trans "Replies" %}</th>
|
||||
<th class="tc3" scope="col">{% trans "Views" %}</th>
|
||||
<th class="tcr">{% trans "Last post" %}</th>
|
||||
<th class="tcmod" scope="col">{% trans "Select" %}</th>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for topic in sticky_topics|forum_unreads:user %}
|
||||
<tr class="isticky">
|
||||
<td class="tcl">
|
||||
<div class="intd">
|
||||
<div class="icon"><div class="nosize"><!-- --></div></div>
|
||||
<div class="tclcon">
|
||||
{% if topic|has_unreads:user %}
|
||||
<strong>{% link topic %} <span class="byuser">{% trans "by" %} {{ topic.user }}</span></strong>
|
||||
{% else %}
|
||||
{% link topic %} <span class="byuser">{% trans "by" %} {{ topic.user }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="tc2">{{ topic.post_count }}</td>
|
||||
<td class="tc3">{{ topic.views }}</td>
|
||||
<td class="tcr"><a href="{{ topic.get_absolute_url }}">{% forum_time topic.updated %}</a> <span class="byuser">{% trans "by" %} {{ topic.last_post.user }}</span></td>
|
||||
<td class="tcmod"><input type="checkbox" name="{{ topic.id }}" value="1" /></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
{% for topic in topics|forum_unreads:user %}
|
||||
<tr {% if topic|has_unreads:user %}class="inew"{% endif %} {% if topic.closed %}class="iclosed"{% endif %}>
|
||||
<td class="tcl">
|
||||
<div class="intd">
|
||||
<div class="icon"><div class="nosize"><!-- --></div></div>
|
||||
<div class="tclcon">
|
||||
{% if topic|has_unreads:user %}
|
||||
<strong>{% link topic %} <span class="byuser">{% trans "by" %} {{ topic.user }}</span></strong>
|
||||
{% else %}
|
||||
{% link topic %} <span class="byuser">{% trans "by" %} {{ topic.user }}</span>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="tc2">{{ topic.reply_count }}</td>
|
||||
<td class="tc3">{{ topic.views }}</td>
|
||||
<td class="tcr"><a href="{{ topic.get_absolute_url }}">{% forum_time topic.updated %}</a> <span class="byuser">оставил {{ topic.last_post.user }}</span></td>
|
||||
<td class="tcmod"><input type="checkbox" name="topic_id[{{ topic.id }}]" value="1" /></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="linksb">
|
||||
<div class="inbox">
|
||||
<p class="pagelink conl">{% pagination %}</p>
|
||||
|
||||
<p class="conr">
|
||||
<input type="submit" name="move_topics" value="{% trans "Move" %}" />
|
||||
<input type="submit" name="delete_topics" value="{% trans "Delete" %}" />
|
||||
<input type="submit" name="open_topics" value="{% trans "Open" %}" />
|
||||
<input type="submit" name="close_topics" value="{% trans "Close" %}" />
|
||||
</p>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
{% endblock %}
|
10
apps/forum/templates/forum/moderators.html
Normal file
10
apps/forum/templates/forum/moderators.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
<em>({% trans "Moderated by" %}</em>
|
||||
{% for moderator in forum.moderators.all %}
|
||||
{% if forloop.last %}
|
||||
{{ moderator|profile_link }})
|
||||
{% else %}
|
||||
{{ moderator|profile_link }},
|
||||
{% endif %}
|
||||
{% endfor %}
|
36
apps/forum/templates/forum/move_topic.html
Normal file
36
apps/forum/templates/forum/move_topic.html
Normal file
|
@ -0,0 +1,36 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="blockform">
|
||||
<h2><span>{% trans "Move topic" %}</span></h2>
|
||||
<div class="box">
|
||||
<form method="get">
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Select destination of move" %}</legend>
|
||||
|
||||
<div class="infldset">
|
||||
<label>{% trans "Move to" %}<br />
|
||||
<select name="to_forum">
|
||||
{% for category in categories %}
|
||||
<optgroup label="{{ category }}">
|
||||
{% for forum in category.forums.all %}
|
||||
<option value="{{ forum.id }}">{{ forum }}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<br /></label>
|
||||
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<p><input type="submit" value="{% trans "Move" %}" /><a href="javascript:history.go(-1)">{% trans "Go back" %}</a></p>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
21
apps/forum/templates/forum/pagination.html
Normal file
21
apps/forum/templates/forum/pagination.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
{% load i18n %}
|
||||
{% ifnotequal pages 1 %}
|
||||
{% trans "Pages" %}:
|
||||
{% if lower_page %}
|
||||
<a href="{{ get_params }}page={{ lower_page }}{#&per_page={{ per_page }}#}">«</a>
|
||||
{% endif %}
|
||||
{% for number in page_list %}
|
||||
{% ifequal number "." %}...
|
||||
{% else %}
|
||||
{% ifequal number page %}
|
||||
<strong>{{ number }}</strong>
|
||||
{% endifequal %}
|
||||
{% ifnotequal number page %}
|
||||
<a href="{{ get_params }}page={{ number }}{#&per_page={{ per_page }}#}">{{ number }}</a>
|
||||
{% endifnotequal %}
|
||||
{% endifequal %}
|
||||
{% endfor %}
|
||||
{% if higher_page %}
|
||||
<a href="{{ get_params }}page={{ higher_page }}{#&per_page={{ per_page }}#}">»</a>
|
||||
{% endif %}
|
||||
{% endifnotequal %}
|
17
apps/forum/templates/forum/pm/base_pm.html
Normal file
17
apps/forum/templates/forum/pm/base_pm.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="block2col">
|
||||
{% include 'forum/pm/menu_pm.html' %}
|
||||
|
||||
{% block sub_content %}
|
||||
{% endblock %}
|
||||
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
{% endblock %}
|
32
apps/forum/templates/forum/pm/create_pm.html
Normal file
32
apps/forum/templates/forum/pm/create_pm.html
Normal file
|
@ -0,0 +1,32 @@
|
|||
{% extends 'forum/pm/base_pm.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block sub_content %}
|
||||
|
||||
|
||||
<div class="blockform">
|
||||
<h2><span>{% trans "Send a message" %}</span></h2>
|
||||
<div class="box">
|
||||
<form method="post" id="post">
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Write your message and submit" %}</legend>
|
||||
|
||||
<div class="infldset txtarea">
|
||||
<label ><strong>{{ form.recipient.label }}</strong><br />{{ form.recipient }}<br /></label>
|
||||
<label><strong>{{ form.subject.label }}</strong><br />{{ form.subject }}<br /></label>
|
||||
<label><strong>{{ form.body.label }}</strong><br />
|
||||
|
||||
{{ form.body }}<br /></label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<p><input type="submit" name="submit" value="{% trans "Send" %}" tabindex="6" accesskey="s" /><a href="javascript:history.go(-1)">{% trans "Go back" %}</a></p>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
48
apps/forum/templates/forum/pm/inbox.html
Normal file
48
apps/forum/templates/forum/pm/inbox.html
Normal file
|
@ -0,0 +1,48 @@
|
|||
{% extends 'forum/pm/base_pm.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block sub_content %}
|
||||
|
||||
<div class="blockform">
|
||||
<h2><span>{% trans "Inbox" %}</span></h2>
|
||||
<div class="box">
|
||||
<div class="inbox">
|
||||
|
||||
<table cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="tcl">{% trans "Subject" %}</th>
|
||||
<th>{% trans "Sender" %}</th>
|
||||
<th class="tcr">{% trans "Date" %}</th>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if messages %}
|
||||
{% for msg in messages %}
|
||||
<tr>
|
||||
|
||||
<td class="tcl">
|
||||
<div class="intd">
|
||||
<div class="icon"><div class="nosize"><!-- --></div></div>
|
||||
<div class="tclcon">
|
||||
<a href="{% url forum_show_pm msg.id %}">{{ msg.subject }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="tc2" style="white-space: nowrap; OVERFLOW: hidden"><a href="{% url forum_profile msg.src_user.username %}">{{ msg.src_user }}</a></td>
|
||||
<td class="tcr" style="white-space: nowrap">{% forum_time msg.created %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr><td class="puncon1" colspan="3">{% trans "No messages" %}</td></tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
14
apps/forum/templates/forum/pm/menu_pm.html
Normal file
14
apps/forum/templates/forum/pm/menu_pm.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
{% load i18n %}
|
||||
<div class="blockmenu">
|
||||
<h2><span>{% trans "Private Messages" %}</span></h2>
|
||||
<div class="box">
|
||||
<div class="inbox">
|
||||
<ul>
|
||||
<li {%ifequal active_menu "inbox" %}class="isactive"{% endifequal %}><a href="{% url forum_pm_inbox %}">{% trans "Inbox" %}</a></li>
|
||||
<li {%ifequal active_menu "outbox" %}class="isactive"{% endifequal %}><a href="{% url forum_pm_outbox %}">{% trans "Sent" %}</a></li>
|
||||
<li {%ifequal active_menu "create" %}class="isactive"{% endifequal %}><a href="{% url forum_create_pm %}">{% trans "Send new message" %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
86
apps/forum/templates/forum/pm/message.html
Normal file
86
apps/forum/templates/forum/pm/message.html
Normal file
|
@ -0,0 +1,86 @@
|
|||
{% extends 'forum/pm/base_pm.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block sub_content %}
|
||||
{% with msg as post %}
|
||||
|
||||
<div id="p2" class="blockpost row_odd firstpost" style="margin-left: 14em;">
|
||||
<h2><span>{% forum_time post.created %}</span></h2>
|
||||
<div class="box">
|
||||
<div class="inbox">
|
||||
<div class="postleft">
|
||||
<dl>
|
||||
<dt><strong>{{ post_user|profile_link }}</strong></dt>
|
||||
<dd>
|
||||
{% if post_user.forum_profile.status %}
|
||||
<strong>{{ post_user.forum_profile.status }}</strong>
|
||||
{% else %}
|
||||
<strong>{{ post_user.forum_profile.group }}</strong>
|
||||
{% endif %}
|
||||
</dd>
|
||||
<dd class="usertitle">
|
||||
{{ post_user|forum_stars }}
|
||||
</dd>
|
||||
{% if post_user.forum_profile.avatar %}
|
||||
<dd class="postavatar"><img src="{{ post_user.forum_profile.avatar.url }}" /></dd>
|
||||
{% endif %}
|
||||
<dd>{% trans "Registered:" %} {{ post_user.date_joined|date:"Y-m-d" }}</dd>
|
||||
<dd>{% trans "Posts:" %} {{ post_user.posts.count }}</dd>
|
||||
{% if moderator %}
|
||||
<dd>{% trans "IP:" %} {{ post_user_ip }}</dd>
|
||||
{% endif %}
|
||||
<dd><a href="{% url reputation post_user %}">{% trans "Reputation" %}</a> : <b>{{ post_user.forum_profile.reply_total }}</b></dd>
|
||||
<dd class="usercontacts"><a href="{% url forum_profile post_user %}">{% trans "Profile" %}</a>
|
||||
{% ifequal post_user.forum_profile.privacy_permission 0 %}
|
||||
<a href="mailto:{{ post_user.email }}">{% trans "E-mail" %}</a>
|
||||
{% endifequal %}
|
||||
{% if user_is_authenticated %}
|
||||
<a href="{% url forum_create_pm %}?recipient={{ post_user.username }}">{% trans "PM" %}</a> </dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
<div class="postright">
|
||||
<div class="postmsg">
|
||||
{{ post.body_html|safe }}
|
||||
{% if not user.is_authenticated or user.forum_profile.show_signatures %}
|
||||
{% if post_user.forum_profile.signature %}
|
||||
<div class="postsignature">
|
||||
<br>
|
||||
---
|
||||
<br>
|
||||
{{ post_user.forum_profile.signature|safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
<div class="postfootleft">
|
||||
{% if post_user|online %}
|
||||
<p><strong>{% trans "Online" %}</strong></p>
|
||||
{% else %}
|
||||
<p>{% trans "Offline" %}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="postfootright">
|
||||
<ul>
|
||||
<li>
|
||||
{% if moderator or post|forum_equal_to:last_post %}
|
||||
{% if moderator or post.user|forum_equal_to:user %}
|
||||
/ <a onclick="return confirm('{% trans "Are you sure you want to delete this post?" %}')" href="{% url delete_post post.id %}">{% trans "Delete" %}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% if inbox %}
|
||||
<li><a href="{% url forum_create_pm %}?recipient={{ post.src_user.username }}">{% trans "Reply" %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
51
apps/forum/templates/forum/pm/outbox.html
Normal file
51
apps/forum/templates/forum/pm/outbox.html
Normal file
|
@ -0,0 +1,51 @@
|
|||
{% extends 'forum/pm/base_pm.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block sub_content %}
|
||||
|
||||
|
||||
<div class="blockform">
|
||||
<h2><span>{% trans "Inbox" %}</span></h2>
|
||||
<div class="box">
|
||||
<div class="inbox">
|
||||
|
||||
<table cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="tcl">{% trans "Subject" %}</th>
|
||||
<th>{% trans "Receiver" %}</th>
|
||||
<th class="tcr">{% trans "Date" %}</th>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if messages %}
|
||||
{% for msg in messages %}
|
||||
<tr>
|
||||
|
||||
<td class="tcl">
|
||||
<div class="intd">
|
||||
<div class="icon"><div class="nosize"><!-- --></div></div>
|
||||
<div class="tclcon">
|
||||
<a href="{% url forum_show_pm msg.id %}">{{ msg.subject }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="tc2" style="white-space: nowrap; OVERFLOW: hidden"><a href="{% url forum_profile msg.dst_user.username %}">{{ msg.dst_user }}</a></td>
|
||||
<td class="tcr" style="white-space: nowrap">{% forum_time msg.created %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr><td class="puncon1" colspan="3">{% trans "No messages" %}</td></tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
43
apps/forum/templates/forum/profile/profile_admin.html
Normal file
43
apps/forum/templates/forum/profile/profile_admin.html
Normal file
|
@ -0,0 +1,43 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div id="profile" class="block2col">
|
||||
{% include 'forum/profile/profile_menu.html' %}
|
||||
<div class="blockform">
|
||||
<h2><span>{{ profile.username }} - {% trans "Administration" %}</span></h2>
|
||||
<div class="box">
|
||||
<form id="profile7" method="post">
|
||||
<div class="inform">
|
||||
|
||||
<fieldset>
|
||||
<legend>{% trans "Delete (administrators only) or ban user" %}</legend>
|
||||
<div class="infldset">
|
||||
<input name="delete_user" value="{% trans "Delete user" %}" type="submit"> <input name="ban" value="{% trans "Ban user" %}" type="submit">
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Set moderator access" %}</legend>
|
||||
<div class="infldset">
|
||||
<p>{% trans "Choose what forums this user should be allowed to moderate. Note: This only applies to moderators. Administrators always have full permissions in all forums." %}</p>
|
||||
<div class="conl">
|
||||
{{ form.forums }}
|
||||
</div>
|
||||
<br class="clearb"><input name="update_forums" value="{% trans "Update forums" %}" type="submit">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
{% endblock %}
|
32
apps/forum/templates/forum/profile/profile_display.html
Normal file
32
apps/forum/templates/forum/profile/profile_display.html
Normal file
|
@ -0,0 +1,32 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div id="profile" class="block2col">
|
||||
{% include 'forum/profile/profile_menu.html' %}
|
||||
<div class="blockform">
|
||||
<h2><span>{{ profile.username }} - {% trans "Display" %}</span></h2>
|
||||
<div class="box">
|
||||
<form id="profile5" method="post">
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Select your preferred style" %}</legend>
|
||||
<div class="infldset">
|
||||
<label>{% trans "If you like you can use a different visual style for this forum." %}<br>
|
||||
|
||||
{{ form.theme }}
|
||||
<br></label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<p><input name="update" value="{% trans "Submit" %}" type="submit">{% trans "When you update your profile, you will be redirected back to this page." %}</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
{% endblock %}
|
82
apps/forum/templates/forum/profile/profile_essentials.html
Normal file
82
apps/forum/templates/forum/profile/profile_essentials.html
Normal file
|
@ -0,0 +1,82 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div id="profile" class="block2col">
|
||||
{% include 'forum/profile/profile_menu.html' %}
|
||||
<div class="blockform">
|
||||
<h2><span>{{ profile.username }} - {% trans "Essentials" %}</span></h2>
|
||||
{{ form.errors }}
|
||||
<div class="box">
|
||||
<form id="profile1" method="post">
|
||||
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Enter your username and password" %}</legend>
|
||||
<div class="infldset">
|
||||
<label><strong>{{ form.username.label }}</strong><br>
|
||||
{% if request.user.is_superuser %}
|
||||
{{ form.username }}
|
||||
{% else %}
|
||||
{{ form.fields.username.initial }}
|
||||
{% endif %}
|
||||
<br></label>
|
||||
<p><a href="{% url auth_password_change %}">{% trans "Change password" %}</a></p>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Enter a valid e-mail address" %}</legend>
|
||||
<div class="infldset">
|
||||
<label><strong>{{ form.email.label }}</strong><br>
|
||||
{{ form.email }}<br></label>
|
||||
<p><a href="{% url misc %}?mail_to={{ profile }}">{% trans "Send e-mail" %}</a></p>
|
||||
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Set your localisation options" %}</legend>
|
||||
<div class="infldset">
|
||||
|
||||
<label>
|
||||
{% trans "Timezone: For the forum to display times correctly you must select your local timezone." %}<br>
|
||||
<br>
|
||||
{{ form.time_zone }}
|
||||
</label>
|
||||
<label>
|
||||
{% trans "Language: You can choose which language you wish to use to view the forum" %}
|
||||
<br>
|
||||
{{ form.language }}
|
||||
<br></label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="inform">
|
||||
|
||||
<fieldset>
|
||||
<legend>{% trans "User activity" %}</legend>
|
||||
<div class="infldset">
|
||||
<p>{% trans "Registered:" %} {{ profile.date_joined|date:"Y-m-d" }}</p>
|
||||
<p>{% trans "Last post:" %} {{ profile.forum_profile.last_post }}</p>
|
||||
<label>{% trans "Posts:" %} {{ form.fields.post_count.initial }} -
|
||||
<a href="{% url search %}?action=show_user&user_id={{ profile.id }}">{% trans "Show all posts" %}</a></p>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<p><input name="update" value="{% trans "Submit" %}" type="submit">{% trans "When you update your profile, you will be redirected back to this page." %}</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
{% endblock %}
|
20
apps/forum/templates/forum/profile/profile_menu.html
Normal file
20
apps/forum/templates/forum/profile/profile_menu.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
{% load i18n %}
|
||||
<div class="blockmenu">
|
||||
<h2><span>{% trans "Profile menu" %}</span></h2>
|
||||
<div class="box">
|
||||
<div class="inbox">
|
||||
<ul>
|
||||
<li {%ifequal active_menu "essentials" %}class="isactive"{% endifequal %}><a href="{% url forum_profile profile %}?section=essentials">{% trans "Essentials" %}</a></li>
|
||||
<li {%ifequal active_menu "personal" %}class="isactive"{% endifequal %}><a href="{% url forum_profile profile %}?section=personal">{% trans "Personal" %}</a></li>
|
||||
<li {%ifequal active_menu "messaging" %}class="isactive"{% endifequal %}><a href="{% url forum_profile profile %}?section=messaging">{% trans "Messaging" %}</a></li>
|
||||
<li {%ifequal active_menu "personality" %}class="isactive"{% endifequal %}><a href="{% url forum_profile profile %}?section=personality">{% trans "Personality" %}</a></li>
|
||||
<li {%ifequal active_menu "display" %}class="isactive"{% endifequal %}><a href="{% url forum_profile profile %}?section=display">{% trans "Display" %}</a></li>
|
||||
<li {%ifequal active_menu "privacy" %}class="isactive"{% endifequal %}><a href="{% url forum_profile profile %}?section=privacy">{% trans "Privacy" %}</a></li>
|
||||
{# if request.user.is_superuser #}
|
||||
<!--li {%ifequal active_menu "admin" %}class="isactive"{% endifequal %}><a href="{% url forum_profile profile %}?section=admin">{% trans "Administration" %}</a></li-->
|
||||
{# endif #}
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
36
apps/forum/templates/forum/profile/profile_messaging.html
Normal file
36
apps/forum/templates/forum/profile/profile_messaging.html
Normal file
|
@ -0,0 +1,36 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div id="profile" class="block2col">
|
||||
{% include 'forum/profile/profile_menu.html' %}
|
||||
<div class="blockform">
|
||||
<h2><span>{{ profile.username }} - {% trans "Messaging" %}</span></h2>
|
||||
<div class="box">
|
||||
<form id="profile3" method="post">
|
||||
<div class="inform">
|
||||
|
||||
<fieldset>
|
||||
<legend>{% trans "Personal" %}</legend>
|
||||
<div class="infldset">
|
||||
<label>{{ form.jabber.label }}<br>{{ form.jabber }}<br></label>
|
||||
<label>{{ form.icq.label }}<br>{{ form.icq }}<br></label>
|
||||
<label>{{ form.msn.label }}<br>{{ form.msn }}<br></label>
|
||||
|
||||
<label>{{ form.aim.label }}<br>{{ form.aim }}<br></label>
|
||||
<label>{{ form.yahoo.label }}<br>{{ form.yahoo }}<br></label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<p><input name="update" value="{% trans "Submit" %}" type="submit">{% trans "When you update your profile, you will be redirected back to this page." %}</p>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
{% endblock %}
|
36
apps/forum/templates/forum/profile/profile_personal.html
Normal file
36
apps/forum/templates/forum/profile/profile_personal.html
Normal file
|
@ -0,0 +1,36 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div id="profile" class="block2col">
|
||||
{% include 'forum/profile/profile_menu.html' %}
|
||||
<div class="blockform">
|
||||
<h2><span>{{ profile.username }} - {% trans "Personal" %}</span></h2>
|
||||
<div class="box">
|
||||
<form id="profile2" method="post">
|
||||
<div class="inform">
|
||||
|
||||
<fieldset>
|
||||
<legend>{% trans "Enter your personal details" %}</legend>
|
||||
<div class="infldset">
|
||||
<label>{{ form.name.label }}<br>{{ form.name }}<br></label>
|
||||
{% if request.user.is_superuser %}
|
||||
<label>{{ form.status.label }} (<em>{% trans "Leave blank to use forum default." %}</em>)<br>{{ form.status }}<br></label>
|
||||
{% endif %}
|
||||
<label>{{ form.location.label }}<br>{{ form.location }}<br></label>
|
||||
<label>{{ form.site.label }}<br>{{ form.site }}<br></label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
</div>
|
||||
<p><input name="update" value="{% trans "Submit" %}" type="submit">{% trans "When you update your profile, you will be redirected back to this page." %}</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
{% endblock %}
|
60
apps/forum/templates/forum/profile/profile_personality.html
Normal file
60
apps/forum/templates/forum/profile/profile_personality.html
Normal file
|
@ -0,0 +1,60 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div id="profile" class="block2col">
|
||||
{% include 'forum/profile/profile_menu.html' %}
|
||||
<div class="blockform">
|
||||
<h2><span>{{ profile.username }} - {% trans "Personality" %}</span></h2>
|
||||
<div class="box">
|
||||
<form id="profile4" method="post">
|
||||
<div class="inform">
|
||||
<fieldset id="profileavatar">
|
||||
<legend>{% trans "Set your avatar display options" %}</legend>
|
||||
<div class="infldset">
|
||||
{% if profile.forum_profile.avatar %}
|
||||
<img src="{{ profile.forum_profile.avatar.url }}" />
|
||||
{% endif %}
|
||||
<p>{% trans "An avatar is a small image that will be displayed with all your posts. You can upload an avatar by clicking the link below. The checkbox 'Use avatar' below must be checked in order for the avatar to be visible in your posts." %}</p>
|
||||
<div class="rbox">
|
||||
<label>{{ form.show_avatar }}{% trans "Use avatar." %}<br></label>
|
||||
</div>
|
||||
{% if profile.forum_profile.avatar %}
|
||||
<p class="clearb"><a href="{% url forum_profile profile.username %}?action=upload_avatar">{% trans "Upload avatar" %}</a>
|
||||
<a href="{% url forum_profile profile.username %}?action=delete_avatar">{% trans "Delete avatar" %}</a></p>
|
||||
{% else %}
|
||||
<p class="clearb"><a href="{% url forum_profile profile.username %}?action=upload_avatar">{% trans "Upload avatar" %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Compose your signature" %}</legend>
|
||||
|
||||
<div class="infldset">
|
||||
<p>{% trans "A signature is a small piece of text that is attached to your posts. In it, you can enter just about anything you like. Perhaps you would like to enter your favourite quote or your star sign. It's up to you! In your signature you can use BBCode if it is allowed in this particular forum. You can see the features that are allowed/enabled listed below whenever you edit your signature." %}</p>
|
||||
<div class="txtarea">
|
||||
<label><br>
|
||||
{{ form.signature }}
|
||||
<br></label>
|
||||
</div>
|
||||
{% if profile.forum_profile.signature %}
|
||||
<p>{{ profile.forum_profile.signature|safe }}</p>
|
||||
{% else %}
|
||||
<p>{% trans "No signature currently stored in profile." %}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<p><input name="update" value="{% trans "Submit" %}" type="submit">{% trans "When you update your profile, you will be redirected back to this page." %}</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
{% endblock %}
|
35
apps/forum/templates/forum/profile/profile_privacy.html
Normal file
35
apps/forum/templates/forum/profile/profile_privacy.html
Normal file
|
@ -0,0 +1,35 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div id="profile" class="block2col">
|
||||
{% include 'forum/profile/profile_menu.html' %}
|
||||
<div class="blockform">
|
||||
<h2><span>{{ profile.username }} - {% trans "Privacy" %}</span></h2>
|
||||
<div class="box">
|
||||
<form id="profile6" method="post">
|
||||
<div class="inform">
|
||||
|
||||
<fieldset>
|
||||
<legend>{% trans "Set your privacy options" %}</legend>
|
||||
<div class="infldset">
|
||||
<input name="form_sent" value="1" type="hidden">
|
||||
<p>{% trans "Select whether you want your e-mail address to be viewable to other users or not and if you want other users to be able to send you e-mail via the forum (form e-mail) or not." %}</p>
|
||||
<div class="rbox">
|
||||
{{ form.privacy_permission }}
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<p><input name="update" value="{% trans "Submit" %}" type="submit">{% trans "When you update your profile, you will be redirected back to this page." %}</p>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
{% endblock %}
|
30
apps/forum/templates/forum/report.html
Normal file
30
apps/forum/templates/forum/report.html
Normal file
|
@ -0,0 +1,30 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="blockform">
|
||||
<h2><span>{% trans "Report post" %}</span></h2>
|
||||
<div class="box">
|
||||
<form id="report" method="post">
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
|
||||
<legend>{% trans "Please enter a short reason why you are reporting this post" %}</legend>
|
||||
<div class="infldset txtarea">
|
||||
{{ form.post }}
|
||||
<label><strong>{% trans "Reason" %}</strong><br />
|
||||
{{ form.reason }}
|
||||
<br /></label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<p><input type="submit" name="submit" value="{% trans "Submit" %}" accesskey="s" /><a href="javascript:history.go(-1)">{% trans "Go back" %}</a></p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
{% endblock %}
|
76
apps/forum/templates/forum/reputation.html
Normal file
76
apps/forum/templates/forum/reputation.html
Normal file
|
@ -0,0 +1,76 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="linkst">
|
||||
<div class="inbox">
|
||||
<p class="pagelink conl">{# pagination #}</p>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="POST">
|
||||
<div class="blockform">
|
||||
<h2><span>{% trans "Reputation of the user" %} {{ profile.user }} <strong>[+{{ profile.reply_count_plus }} / -{{ profile.reply_count_minus }}] </strong></span></h2>
|
||||
<div class="box">
|
||||
<div class="inbox">
|
||||
<table cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
<th class="tc3" style="width: 15%;">{% trans "From user" %}</th>
|
||||
<th class="tc3" style="width: 15%;">{% trans "For topic" %}</th>
|
||||
<th class="tc3" style="width: 35%;">{% trans "Reason" %}</th>
|
||||
<th class="tc3" style="width: 10%; text-align: center;">{% trans "Estimation" %}</th>
|
||||
<th class="tc3" style="width: 15%;">{% trans "Date" %}</th>
|
||||
{% if request.user.is_superuser %}
|
||||
<th class="tc3" style="width: 10%;">{% trans "Delete" %}</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
|
||||
</thead><tbody>
|
||||
{% for reputation in reputations %}
|
||||
<tr>
|
||||
<td><a href="{% url reputation reputation.from_user %}">{{ reputation.from_user }}</a></td>
|
||||
<td>{% link reputation.topic %}</td>
|
||||
<td>{{ reputation.reason }}</td>
|
||||
<td style="text-align: center;">
|
||||
{% ifequal reputation.sign 1 %}
|
||||
<img src="{{ MEDIA_URL }}forum/img/reputation/warn_add.gif" alt="+" border="0">
|
||||
{% else %}
|
||||
<img src="{{ MEDIA_URL }}forum/img/reputation/warn_minus.gif" alt="+" border="0">
|
||||
{% endifequal %}
|
||||
</td>
|
||||
<td>{% forum_time reputation.time %}</td>
|
||||
{% if request.user.is_superuser %}
|
||||
<td style="text-align: center;"><input name="reputation_id[{{ reputation.id }}]" type="checkbox"></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearer"></div>
|
||||
<div class="postlinksb">
|
||||
<div class="inbox">
|
||||
|
||||
<p class="pagelink conl">{# pagination #}</p>
|
||||
<p class="postlink conr">
|
||||
{% if request.user.is_superuser %}
|
||||
<input name="del_reputation" value="Delete" onclick="return confirm('{% trans "Are you sure" %}?')" type="submit">
|
||||
{% endif %}
|
||||
<a href="javascript:history.go(-1)">{% trans "Go back" %}</a>
|
||||
</p>
|
||||
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
{% endblock %}
|
51
apps/forum/templates/forum/reputation_form.html
Normal file
51
apps/forum/templates/forum/reputation_form.html
Normal file
|
@ -0,0 +1,51 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="blockform">
|
||||
<h2><span>{% trans "Please, fill the form" %}</span></h2>
|
||||
<div class="box">
|
||||
<div class="inbox">
|
||||
<form action="{% url reputation form.to_user %}" method="post">
|
||||
<table cellspacing="0">
|
||||
{{ form.topic }}
|
||||
{{ form.sign }}
|
||||
<tbody><tr>
|
||||
<td class="tc4" width="30%">{% trans "Your name:" %}</td>
|
||||
<td class="tc4" width="70%">{{ form.from_user }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tc4" width="30%">{% trans "To whom we change a reputation:" %}</td>
|
||||
<td class="tc4" width="70%">{{ form.to_user }}</td>
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tc4" width="30%">{% trans "The reason of change of reputation:" %}</td>
|
||||
<td class="tc4" width="70%">{{ form.reason }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tc4" width="30%">{% trans "Method:" %}</td>
|
||||
<td class="tc4" width="70%">
|
||||
{% ifequal request.GET.action "plus" %}
|
||||
{% trans "Add of a reputation" %}
|
||||
{% else %}
|
||||
{% trans "Reduction of a reputation" %}
|
||||
{% endifequal %}
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<table cellspacing="0">
|
||||
<tbody><tr>
|
||||
<td class="tc4" style="text-align: center;"><input name="submit" value="{% trans "Submit" %}" type="submit"> <a href="javascript:history.go(-1)">{% trans "Go back" %}</a></td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
{% endblock %}
|
78
apps/forum/templates/forum/search_form.html
Normal file
78
apps/forum/templates/forum/search_form.html
Normal file
|
@ -0,0 +1,78 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div id="searchform" class="blockform">
|
||||
<h2><span>{% trans "Search" %}</span></h2>
|
||||
<div class="box">
|
||||
<form id="search" method="get">
|
||||
<input type="hidden" name="action" value="search" />
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Enter your search criteria" %}</legend>
|
||||
<div class="infldset">
|
||||
<label class="conl">{{ form.keywords.label }}<br />{{ form.keywords }}<br /></label>
|
||||
<label class="conl">{{ form.author.label }}<br />{{ form.author }}<br /></label>
|
||||
|
||||
<p class="clearb">{% trans "To search by keyword, enter a term or terms to search for. Separate terms with spaces. To search by author enter the username of the author whose posts you wish to search for." %}</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Select where to search" %}</legend>
|
||||
<div class="infldset">
|
||||
|
||||
<label class="conl">{{ form.forum.label }}
|
||||
<br />
|
||||
<select id="forum" name="forum">
|
||||
<option value="0">{% trans "All forums" %}</option>
|
||||
{% for category in categories %}
|
||||
<optgroup label="{{ category }}">
|
||||
{% for forum in category.forums.all %}
|
||||
<option value="{{ forum.id }}">{{ forum }}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<br /></label>
|
||||
<label class="conl">{{ form.search_in.label }}
|
||||
<br />
|
||||
{{ form.search_in }}
|
||||
<br /></label>
|
||||
<p class="clearb">{% trans "Choose in which forum you would like to search and if you want to search in topic subjects, message text or both." %}</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
|
||||
<legend>{% trans "Select how to view search results" %}</legend>
|
||||
<div class="infldset">
|
||||
<label class="conl">{{ form.sort_by.label }}
|
||||
<br />
|
||||
{{ form.sort_by }}
|
||||
<br /></label>
|
||||
<label class="conl">{{ form.sort_dir.label }}
|
||||
<br />
|
||||
{{ form.sort_dir }}
|
||||
|
||||
<br /></label>
|
||||
<label class="conl">{{ form.show_as.label }}
|
||||
<br />
|
||||
{{ form.show_as }}
|
||||
<br /></label>
|
||||
<p class="clearb">{% trans "You can choose how you wish to sort and show your results." %}</p>
|
||||
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<p><input type="submit" name="search" value="{% trans "Submit" %}" accesskey="s" /></p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
{% endblock %}
|
60
apps/forum/templates/forum/search_posts.html
Normal file
60
apps/forum/templates/forum/search_posts.html
Normal file
|
@ -0,0 +1,60 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="linkst">
|
||||
<div class="inbox">
|
||||
<p class="pagelink"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% for post in posts %}
|
||||
<div class="blockpost searchposts roweven">
|
||||
<h2>{% link post.topic.forum %} » {% link post.topic %} » <a href="{{ post.get_absolute_url }}">{% forum_time post.created %}</a></h2>
|
||||
|
||||
<div class="box">
|
||||
<div class="inbox">
|
||||
<div class="postleft">
|
||||
<dl>
|
||||
<dt><strong><a href="{% url forum_profile post.user %}">{{ post.user }}</a></strong></dt>
|
||||
<dd>{% trans "Replies:" %} {{ post.topic.post_count }}</dd>
|
||||
<dd><div class="icon"><div class="nosize"><!-- --></div></div>
|
||||
</dd>
|
||||
|
||||
<dd><p class="clearb"><a href="{{ post.get_absolute_url }}">{% trans "Go to post" %}</a></p></dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="postright">
|
||||
<div class="postmsg">
|
||||
{{ post.body_html|safe }}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="postlinksb">
|
||||
<div class="inbox">
|
||||
<p class="pagelink"></p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
<dl id="searchlinks" class="conl">
|
||||
<dt><strong>{% trans "Search links" %}</strong></dt>
|
||||
<dd><a href="{% url search %}?action=show_24h">{% trans "Show recent posts" %}</a></dd>
|
||||
<dd><a href="{% url search %}?action=show_unanswered">{% trans "Show unanswered posts" %}</a></dd>
|
||||
{% if user.is_authenticated %}
|
||||
<dd><a href="{% url search %}?action=show_subscriptions">{% trans "Show your subscribed topics" %}</a></dd>
|
||||
<dd><a href="{% url search %}?action=show_user&user_id={{ request.user.id }}">{% trans "Show your posts" %}</a></dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
{% endblock %}
|
||||
|
||||
|
71
apps/forum/templates/forum/search_topics.html
Normal file
71
apps/forum/templates/forum/search_topics.html
Normal file
|
@ -0,0 +1,71 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="linkst">
|
||||
<div class="inbox">
|
||||
<p class="pagelink"></p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="vf" class="blocktable">
|
||||
<h2><span>{% trans "Search results" %}</span></h2>
|
||||
<div class="box">
|
||||
<div class="inbox">
|
||||
<table cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
<th class="tcl" scope="col">{% trans "Topic" %}</th>
|
||||
<th class="tc2" scope="col">{% trans "Forum" %}</th>
|
||||
<th class="tc3" scope="col">{% trans "Replies" %}</th>
|
||||
<th class="tcr" scope="col">{% trans "Last post" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for topic in topics %}
|
||||
<tr {% if topic|has_unreads:user %}class="inew"{% endif %} {% if topic.closed %}class="iclosed"{% endif %}>
|
||||
<td class="tcl">
|
||||
<div class="intd">
|
||||
<div class="icon"><div class="nosize"><!-- --></div></div>
|
||||
<div class="tclcon">
|
||||
{% if topic|has_unreads:user %}
|
||||
<strong>{% link topic %} <span class="byuser">{% trans "by" %} {{ topic.user }}</span></strong>
|
||||
{% else %}
|
||||
{% link topic %} <span class="byuser">{% trans "by" %} {{ topic.user }}</span>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="tc2">{% link topic.forum %}</td>
|
||||
<td class="tc3">{{ topic.reply_count }}</td>
|
||||
<td class="tcr"><a href="{{ topic.get_absolute_url }}">{% forum_time topic.updated %}</a> <span class="byuser">{% trans "by" %} {{ topic.last_post.user }}</span></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="linksb">
|
||||
<div class="inbox">
|
||||
<p class="pagelink"></p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
<dl id="searchlinks" class="conl">
|
||||
<dt><strong>{% trans "Search links" %}</strong></dt>
|
||||
<dd><a href="{% url search %}?action=show_24h">{% trans "Show recent posts" %}</a></dd>
|
||||
<dd><a href="{% url search %}?action=show_unanswered">{% trans "Show unanswered posts" %}</a></dd>
|
||||
{% if user.is_authenticated %}
|
||||
<dd><a href="{% url search %}?action=show_subscriptions">{% trans "Show your subscribed topics" %}</a></dd>
|
||||
<dd><a href="{% url search %}?action=show_user&user_id={{ request.user.id }}">{% trans "Show your posts" %}</a></dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
{% endblock %}
|
193
apps/forum/templates/forum/topic.html
Normal file
193
apps/forum/templates/forum/topic.html
Normal file
|
@ -0,0 +1,193 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="linkst">
|
||||
<div class="inbox">
|
||||
<p class="pagelink conl">{% pagination %}</p>
|
||||
{% if not topic.closed and user.is_authenticated %}
|
||||
<p class="postlink conr"><a href="{% url add_post topic.id %}">Ответить</a></p>
|
||||
{% endif %}
|
||||
<ul><li><a href="{% url index %}">{% trans "Root" %} </a></li><li>» {% link topic.forum %} </li><li>» {{ topic }}
|
||||
<a href="{% url forum_feed "topic" %}{{ topic.id }}"><img src="{{ MEDIA_URL }}/forum/img/feed-icon-small.png" alt="[RSS Feed]" title="[RSS Feed]" style="vertical-align:middle;" /></a>
|
||||
</li></ul>
|
||||
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% for post in posts %}
|
||||
<div id="p{{ post.id }}" class="blockpost roweven firstpost">
|
||||
<a name="post-{{ post.id }}"></a>
|
||||
<h2><span><span class="conr">#{{ forloop.counter }} </span><a href="{{ post.get_absolute_url }}">{% forum_time post.created %}</a></span></h2>
|
||||
<div class="box">
|
||||
<div class="inbox">
|
||||
<div class="postleft">
|
||||
|
||||
<dl>
|
||||
<dt><strong><a href="javascript:pasteN('{{ post.user }}');">{{ post.user }}</a></strong></dt>
|
||||
<dd class="usertitle">
|
||||
{% if post.user.forum_profile.status %}
|
||||
<strong>{{ post.user.forum_profile.status }}</strong>
|
||||
{% else %}
|
||||
<strong>{{ post.user.forum_profile.group }}</strong>
|
||||
{% endif %}
|
||||
</dd>
|
||||
<dd class="usertitle">
|
||||
{{ post.user|forum_stars }}
|
||||
</dd>
|
||||
{% if post.user.forum_profile.avatar and post.user.forum_profile.show_avatar %}
|
||||
<dd class="postavatar"><img src="{{ post.user.forum_profile.avatar.url }}" /></dd>
|
||||
{% endif %}
|
||||
{% if post.user.forum_profile.location %}
|
||||
<dd>{% trans "From:" %} {{ post.user.forum_profile.location }}</dd>
|
||||
{% endif %}
|
||||
<dd>{% trans "Registered:" %} {{ post.user.date_joined|date:"Y-m-d" }}</dd>
|
||||
<dd>{% trans "Posts:" %} {{ post.user.posts.count }}</dd>
|
||||
{% if moderator %}
|
||||
<dd>{% trans "IP:" %} {{ post.user_ip }}</dd>
|
||||
{% endif %}
|
||||
{% ifnotequal request.user post.user %}
|
||||
<dd><a href="{% url reputation post.user %}">{% trans "Reputation" %}</a> : <a href="{% url reputation post.user %}?action=plus&topic_id={{ post.topic.id }}"><img src="{{ MEDIA_URL }}forum/img/reputation/warn_add.gif" alt="+" border="0"></a> <strong>{{ post.user.forum_profile.reply_total }} </strong><a href="{% url reputation post.user %}?action=minus&topic_id={{ post.topic.id }}"><img src="{{ MEDIA_URL }}forum/img/reputation/warn_minus.gif" alt="-" border="0"></a></dd>
|
||||
{% endifnotequal %}
|
||||
<dd class="usercontacts"><a href="{% url forum_profile post.user %}">{% trans "Profile" %}</a>
|
||||
{% ifequal post.user.forum_profile.privacy_permission 0 %}
|
||||
<a href="mailto:{{ post.user.email }}">{% trans "E-mail" %}</a>
|
||||
{% else %}
|
||||
{% ifequal post.user.forum_profile.privacy_permission 1 %}
|
||||
<a href="{% url misc %}?mail_to={{ post.user }}">{% trans "Send e-mail" %}</a>
|
||||
{% endifequal %}
|
||||
{% endifequal %}
|
||||
{% if user.is_authenticated %}
|
||||
<a href="{% url forum_create_pm %}?recipient={{ post.user.username }}">{% trans "PM" %}</a> </dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
<div class="postright">
|
||||
<h3>{{ post.topic.name }}</h3>
|
||||
|
||||
<div class="postmsg">
|
||||
{{ post.body_html|safe }}
|
||||
{% if not user.is_authenticated or user.forum_profile.show_signatures %}
|
||||
{% if post.user.forum_profile.signature %}
|
||||
<div class="postsignature">
|
||||
<br>
|
||||
---
|
||||
<br>
|
||||
{{ post.user.forum_profile.signature|safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if post.updated %}
|
||||
<p class="postedit"><em>{% trans "Edited" %} {{ post.user }} ({% forum_time post.updated %})</em></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearer"></div>
|
||||
<div class="postfootleft">
|
||||
{% if post.user|online %}
|
||||
<p><strong>{% trans "Online" %}</strong></p>
|
||||
{% else %}
|
||||
<p>{% trans "Offline" %}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="postfootright">
|
||||
<ul>
|
||||
<li class="postreport"><a href="{% url misc %}?action=report&post_id={{ post.id }}">{% trans "Report" %}</a> | </li>
|
||||
{% if moderator or post|forum_equal_to:last_post %}
|
||||
{% if moderator or post.user|forum_equal_to:user %}
|
||||
<li class="postdelete"><a onclick="return confirm('{% trans "Are you sure you want to delete this post?" %}')" href="{% url delete_post post.id %}">{% trans "Delete" %}</a> | </li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if moderator or post|forum_posted_by:user %}
|
||||
<li class="postedit"><a href="{% url edit_post post.id %}">{% trans "Edit" %}</a> | </li>
|
||||
{% endif %}
|
||||
<li class="postquote"><a href="{% url add_post topic.id %}?post_id={{ post.id }}">{% trans "Reply" %}</a> | </li>
|
||||
<li class="postquote"><a onmouseover="copyQ('{{ post.user }}');" href="javascript:pasteQ();">{% trans "Quote" %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="postlinksb">
|
||||
<div class="inbox">
|
||||
<p class="pagelink conl">{% pagination %}</p>
|
||||
<p class="postlink conr"><a href="{% url add_post topic.id %}">{% trans "Reply" %}</a></p>
|
||||
<ul><li><a href="{% url index %}">{% trans "Root" %} </a></li><li>» {% link topic.forum %} </li><li>» {{ topic }}
|
||||
<a href="{% url forum_feed "topic" %}{{ topic.id }}"><img src="{{ MEDIA_URL }}/forum/img/feed-icon-small.png" alt="[RSS Feed]" title="[RSS Feed]" style="vertical-align:middle;" /></a>
|
||||
</li></ul>
|
||||
{% if user.is_authenticated %}
|
||||
{% if subscribed %}
|
||||
<a class="subscribelink clearb" href="{% url forum_delete_subscription topic.id %}?from_topic">{% trans "Unsubscribe" %}</a>
|
||||
{% else %}
|
||||
<a class="subscribelink clearb" href="{% url forum_add_subscription topic.id %}">{% trans "Subscribe" %}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if not topic.closed and user.is_authenticated %}
|
||||
<div class="blockform">
|
||||
<h2><span>{% trans "Quick post" %}</span></h2>
|
||||
<div class="box">
|
||||
<form id="post" action="{% url add_post topic.id %}" method="post">
|
||||
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Write your message and submit" %}</legend>
|
||||
<div class="infldset txtarea">
|
||||
{{ form.body }}
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</div>
|
||||
<p><input type="submit" value="{% trans "Submit" %}" /></p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
<div class="conl">
|
||||
<form id="qjump" method="GET" action="forum">
|
||||
<div><label>{% trans "Jump to " %}
|
||||
<br />
|
||||
<select name="id" id="forum_id" onchange="window.location=('/forum/'+this.options[this.selectedIndex].value)">
|
||||
{% for category in categories %}
|
||||
<optgroup label="{{ category }}">
|
||||
{% for forum in category.forums.all %}
|
||||
<option value="{{ forum.id }}">{{ forum }}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="button" onclick="window.location=('/forum/'+getElementById('forum_id').value)" value=" {% trans "Go" %} " accesskey="g" />
|
||||
</label></div>
|
||||
|
||||
</form>
|
||||
<dl id="modcontrols"><dt><strong>{% trans "Moderator control" %}</strong></dt>
|
||||
|
||||
{% if moderator %}
|
||||
<dd><a href="{% url delete_posts topic.id %}">{% trans "Delete multiple posts" %}</a></dd>
|
||||
|
||||
<dd><a href="{% url move_topic topic.id %}">{% trans "Move topic" %}</a></dd>
|
||||
|
||||
{% if topic.closed %}
|
||||
<dd><a href="{% url open_topic topic.id %}">{% trans "Open topic" %}</a></dd>
|
||||
{% else %}
|
||||
<dd><a href="{% url close_topic topic.id %}">{% trans "Close topic" %}</a></dd>
|
||||
{% endif %}
|
||||
{% if topic.sticky %}
|
||||
<dd><a href="{% url unstick_topic topic.id %}">{% trans "Unstick topic" %}</a></dd></dl>
|
||||
{% else %}
|
||||
<dd><a href="{% url stick_topic topic.id %}">{% trans "Stick topic" %}</a></dd></dl>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
30
apps/forum/templates/forum/upload_avatar.html
Normal file
30
apps/forum/templates/forum/upload_avatar.html
Normal file
|
@ -0,0 +1,30 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="blockform">
|
||||
<h2><span>{% trans "Upload avatar" %}</span></h2>
|
||||
<div class="box">
|
||||
<form id="upload_avatar" method="post" enctype="multipart/form-data">
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Enter an avatar file to upload" %}</legend>
|
||||
|
||||
<div class="infldset">
|
||||
<label><strong>{{ form.avatar.label }}</strong><br />
|
||||
{{ form.avatar }}
|
||||
<br /></label>
|
||||
<p>{% blocktrans %}An avatar is a small image that will be displayed under your username in your posts. It must not be any bigger than {{ avatar_width }} x {{ avatar_height }} pixels {% endblocktrans %}</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<p><input type="submit" name="upload" value="{% trans "Upload" %}" /><a href="javascript:history.go(-1)">{% trans "Go back" %}</a></p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
{% endblock %}
|
147
apps/forum/templates/forum/user.html
Normal file
147
apps/forum/templates/forum/user.html
Normal file
|
@ -0,0 +1,147 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div id="viewprofile" class="block">
|
||||
<h2><span>{% trans "Profile" %}</span></h2>
|
||||
<div class="box">
|
||||
<div class="fakeform">
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Personal" %}</legend>
|
||||
|
||||
<div class="infldset">
|
||||
<dl>
|
||||
<dt>{% trans "Username:" %} </dt>
|
||||
<dd>{{ profile }}</dd>
|
||||
<dt>{% trans "Title" %} </dt>
|
||||
<dd>{{ profile.forum_profile.status }}</dd>
|
||||
|
||||
|
||||
<dt>{% trans "Real name:" %} </dt>
|
||||
{% if profile.first_name or profile.last_name %}
|
||||
<dd>{{ profile.first_name }} {{ profile.last_name }}</dd>
|
||||
{% else %}
|
||||
<dd>{% trans "(Unknown)" %}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Location" %} </dt>
|
||||
{% if profile.forum_profile.location %}
|
||||
<dd>{{ profile.forum_profile.location }}</dd>
|
||||
{% else %}
|
||||
<dd>{% trans "(Unknown)" %}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Website:" %} </dt>
|
||||
{% if profile.forum_profile.site %}
|
||||
<dd>{{ profile.forum_profile.site|urlize }}</dd>
|
||||
{% else %}
|
||||
<dd>{% trans "(Unknown)" %}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "E-mail:" %} </dt>
|
||||
{% ifequal profile.forum_profile.privacy_permission 0 %}
|
||||
<dd><a href="mailto:{{ profile.email }}">{% trans "E-mail" %}</a></dd>
|
||||
{% else %}
|
||||
{% ifequal profile.forum_profile.privacy_permission 1 %}
|
||||
<dd><a href="{% url misc %}?mail_to={{ profile }}">{% trans "Send e-mail" %}</a></dd>
|
||||
{% else %}
|
||||
<dd>{% trans "(Unknown)" %}</dd>
|
||||
{% endifequal %}
|
||||
{% endifequal %}
|
||||
<dd>{{ profile.forum_profile.user.email }}</dd>
|
||||
</dl>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Messaging" %}</legend>
|
||||
<div class="infldset">
|
||||
<dl>
|
||||
<dt>{% trans "Jabber:" %} </dt>
|
||||
{% if profile.forum_profile.jabber %}
|
||||
<dd>{{ profile.forum_profile.jabber }}</dd>
|
||||
{% else %}
|
||||
<dd>{% trans "(Unknown)" %}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "ICQ:" %} </dt>
|
||||
{% if profile.forum_profile.icq %}
|
||||
<dd>{{ profile.forum_profile.icq }}</dd>
|
||||
{% else %}
|
||||
<dd>{% trans "(Unknown)" %}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "MSN Messenger:" %} </dt>
|
||||
{% if profile.forum_profile.msn %}
|
||||
<dd>{{ profile.forum_profile.msn }}</dd>
|
||||
{% else %}
|
||||
<dd>{% trans "(Unknown)" %}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "AOL IM:" %} </dt>
|
||||
{% if profile.forum_profile.aim %}
|
||||
<dd>{{ profile.forum_profile.aim }}</dd>
|
||||
{% else %}
|
||||
<dd>{% trans "(Unknown)" %}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Yahoo! Messenger:" %} </dt>
|
||||
{% if profile.forum_profile.yahoo %}
|
||||
<dd>{{ profile.forum_profile.yahoo }}</dd>
|
||||
{% else %}
|
||||
<dd>{% trans "(Unknown)" %}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
</div>
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Personality" %}</legend>
|
||||
<div class="infldset">
|
||||
<dl>
|
||||
<dt>{% trans "Avatar:" %} </dt>
|
||||
{% if profile.forum_profile.avatar %}
|
||||
<dd><img src="{{ profile.forum_profile.avatar.url }}" /></dd>
|
||||
{% else %}
|
||||
<dd>{% trans "(No avatar)" %}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Signature:" %} </dt>
|
||||
{% if profile.forum_profile.signature %}
|
||||
<dd>{{ profile.forum_profile.signature|safe }}</dd>
|
||||
{% else %}
|
||||
<dd>{% trans "(Unknown)" %}</dd>
|
||||
{% endif %}</dd>
|
||||
|
||||
</dl>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "User activity" %}</legend>
|
||||
|
||||
<div class="infldset">
|
||||
<dl>
|
||||
<dt>{% trans "Posts:" %} </dt>
|
||||
<dd>{{ profile.posts.all.count }} - <a href="{% url search %}?action=show_user&user_id={{ profile.id }}">Показать все сообщения</a></dd>
|
||||
<dt>{% trans "Last post:" %} </dt>
|
||||
{% if profile.forum_profile.last_post %}
|
||||
<dd>{{ profile.forum_profile.last_post }}</dd>
|
||||
{% else %}
|
||||
<dd>{% trans "(Unknown)" %}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt>{% trans "Registered:" %} </dt>
|
||||
<dd>{{ profile.date_joined|date:"Y-m-d" }}</dd>
|
||||
</dl>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
78
apps/forum/templates/forum/users.html
Normal file
78
apps/forum/templates/forum/users.html
Normal file
|
@ -0,0 +1,78 @@
|
|||
{% extends 'forum/base.html' %}
|
||||
{% load forum_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="blockform">
|
||||
<h2><span>{% trans "User search" %}</span></h2>
|
||||
<div class="box">
|
||||
<form method="get" id="userlist">
|
||||
<div class="inform">
|
||||
<fieldset>
|
||||
<legend>{% trans "Find and sort users" %}</legend>
|
||||
<div class="infldset">
|
||||
|
||||
<label class="conl">{{ form.username.label }}<br />{{ form.username }}<br /></label>
|
||||
|
||||
<label class="conl">{{ form.sort_by.label }}
|
||||
<br />
|
||||
{{ form.sort_by }}
|
||||
|
||||
<br /></label>
|
||||
<label class="conl">{{ form.sort_dir.label }}
|
||||
<br />
|
||||
{{ form.sort_dir }}
|
||||
|
||||
<br /></label>
|
||||
<p class="clearb">{% trans "Enter a username to search for. The username field can be left blank. Sort users by name, date registered or number of posts and in ascending/descending order." %}</p>
|
||||
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<input type="submit" value="{% trans "Search" %}" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="linkst">
|
||||
<div class="inbox">
|
||||
{% pagination %}
|
||||
</div>
|
||||
</div>
|
||||
<div id="users1" class="blocktable">
|
||||
<h2><span>{% trans "User list" %}</span></h2>
|
||||
<div class="box">
|
||||
<div class="inbox">
|
||||
<table cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
<th class="tcl" scope="col">{% trans "Username" %}</th>
|
||||
<th class="tc2" scope="col">{% trans "Title" %}</th>
|
||||
<th class="tc3" scope="col">{% trans "Posts" %}</th>
|
||||
<th class="tcr" scope="col">{% trans "Registered" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for profile in users %}
|
||||
<tr>
|
||||
<td class="tcl">{{ profile|profile_link }}</td>
|
||||
<td class="tc2">{{ profile.forum_profile.status }}</td>
|
||||
<td class="tc3">{{ profile.posts.count }}</td>
|
||||
<td class="tcr">{{ profile.date_joined|date:"d-m-Y" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="linkst">
|
||||
<div class="inbox">
|
||||
{% pagination %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block controls %}
|
||||
{% endblock %}
|
0
apps/forum/templatetags/__init__.py
Normal file
0
apps/forum/templatetags/__init__.py
Normal file
232
apps/forum/templatetags/forum_extras.py
Normal file
232
apps/forum/templatetags/forum_extras.py
Normal file
|
@ -0,0 +1,232 @@
|
|||
# -*- coding: utf-8
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django import template
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.cache import cache
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.template import RequestContext
|
||||
from django.utils.encoding import smart_unicode
|
||||
from django.db import settings
|
||||
from django.utils.html import escape
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils import dateformat
|
||||
|
||||
from apps.forum.models import Forum, Topic, Post, Read, PrivateMessage, Report
|
||||
from apps.forum.unread import cache_unreads
|
||||
|
||||
register = template.Library()
|
||||
|
||||
# TODO:
|
||||
# * rename all tags with forum_ prefix
|
||||
|
||||
@register.filter
|
||||
def profile_link(user):
|
||||
data = u'<a href="%s">%s</a>' % (\
|
||||
reverse('forum_profile', args=[user.username]), user.username)
|
||||
return mark_safe(data)
|
||||
|
||||
|
||||
@register.tag
|
||||
def forum_time(parser, token):
|
||||
try:
|
||||
tag, time = token.split_contents()
|
||||
except ValueError:
|
||||
raise template.TemplateSyntaxError('forum_time requires single argument')
|
||||
else:
|
||||
return ForumTimeNode(time)
|
||||
|
||||
class ForumTimeNode(template.Node):
|
||||
def __init__(self, time):
|
||||
self.time = template.Variable(time)
|
||||
|
||||
def render(self, context):
|
||||
time = self.time.resolve(context)
|
||||
delta = datetime.now() - time
|
||||
today = datetime.now().replace(hour=0, minute=0, second=0)
|
||||
yesterday = today - timedelta(days=1)
|
||||
|
||||
if time > today:
|
||||
return u'Сегодня %s' % time.strftime('%H:%M:%S')
|
||||
elif time > yesterday:
|
||||
return u'Вчера %s' % time.strftime('%H:%M:%S')
|
||||
else:
|
||||
return time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# TODO: this old code requires refactoring
|
||||
@register.inclusion_tag('forum/pagination.html',takes_context=True)
|
||||
def pagination(context, adjacent_pages=1):
|
||||
"""
|
||||
Return the list of A tags with links to pages.
|
||||
"""
|
||||
|
||||
page_list = range(
|
||||
max(1,context['page'] - adjacent_pages),
|
||||
min(context['pages'],context['page'] + adjacent_pages) + 1)
|
||||
lower_page = None
|
||||
higher_page = None
|
||||
|
||||
if not 1 == context['page']:
|
||||
lower_page = context['page'] - 1
|
||||
|
||||
if not 1 in page_list:
|
||||
page_list.insert(0,1)
|
||||
if not 2 in page_list:
|
||||
page_list.insert(1,'.')
|
||||
|
||||
if not context['pages'] == context['page']:
|
||||
higher_page = context['page'] + 1
|
||||
|
||||
if not context['pages'] in page_list:
|
||||
if not context['pages'] - 1 in page_list:
|
||||
page_list.append('.')
|
||||
page_list.append(context['pages'])
|
||||
get_params = '&'.join(['%s=%s' % (x[0],','.join(x[1])) for x in
|
||||
context['request'].GET.iteritems() if (not x[0] == 'page' and not x[0] == 'per_page')])
|
||||
if get_params:
|
||||
get_params = '?%s&' % get_params
|
||||
else:
|
||||
get_params = '?'
|
||||
|
||||
return {
|
||||
'get_params': get_params,
|
||||
'lower_page': lower_page,
|
||||
'higher_page': higher_page,
|
||||
'page': context['page'],
|
||||
'pages': context['pages'],
|
||||
'page_list': page_list,
|
||||
'per_page': context['per_page'],
|
||||
}
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def link(object, anchor=u''):
|
||||
"""
|
||||
Return A tag with link to object.
|
||||
"""
|
||||
|
||||
url = hasattr(object,'get_absolute_url') and object.get_absolute_url() or None
|
||||
anchor = anchor or smart_unicode(object)
|
||||
return mark_safe('<a href="%s">%s</a>' % (url, escape(anchor)))
|
||||
|
||||
|
||||
@register.filter
|
||||
def has_unreads(topic, user):
|
||||
"""
|
||||
Check if topic has messages which user didn't read.
|
||||
"""
|
||||
|
||||
now = datetime.now()
|
||||
delta = timedelta(seconds=settings.FORUM_READ_TIMEOUT)
|
||||
|
||||
if not user.is_authenticated():
|
||||
return False
|
||||
else:
|
||||
if isinstance(topic, Topic):
|
||||
if (now - delta > topic.updated):
|
||||
return False
|
||||
else:
|
||||
if hasattr(topic, '_read'):
|
||||
read = topic._read
|
||||
else:
|
||||
try:
|
||||
read = Read.objects.get(user=user, topic=topic)
|
||||
except Read.DoesNotExist:
|
||||
read = None
|
||||
|
||||
if read is None:
|
||||
return True
|
||||
else:
|
||||
return topic.updated > read.time
|
||||
else:
|
||||
raise Exception('Object should be a topic')
|
||||
|
||||
|
||||
@register.filter
|
||||
def forum_setting(name):
|
||||
return mark_safe(getattr(settings, name, 'NOT DEFINED'))
|
||||
|
||||
|
||||
@register.filter
|
||||
def forum_moderated_by(topic, user):
|
||||
"""
|
||||
Check if user is moderator of topic's forum.
|
||||
"""
|
||||
|
||||
return user.is_superuser or user in topic.forum.moderators.all()
|
||||
|
||||
|
||||
@register.filter
|
||||
def forum_editable_by(post, user):
|
||||
"""
|
||||
Check if the post could be edited by the user.
|
||||
"""
|
||||
|
||||
if user.is_superuser:
|
||||
return True
|
||||
if post.user == user:
|
||||
return True
|
||||
if user in post.topic.forum.moderators.all():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@register.filter
|
||||
def forum_posted_by(post, user):
|
||||
"""
|
||||
Check if the post is writed by the user.
|
||||
"""
|
||||
|
||||
return post.user == user
|
||||
|
||||
|
||||
@register.filter
|
||||
def forum_equal_to(obj1, obj2):
|
||||
"""
|
||||
Check if objects are equal.
|
||||
"""
|
||||
|
||||
return obj1 == obj2
|
||||
|
||||
|
||||
@register.filter
|
||||
def forum_unreads(qs, user):
|
||||
return cache_unreads(qs, user)
|
||||
|
||||
|
||||
@register.filter
|
||||
def forum_stars(user):
|
||||
if user.posts.count() >= settings.FORUM_STAR_5:
|
||||
return mark_safe('<img src="%sforum/img/stars/Star_5.gif" alt="" >' % (settings.MEDIA_URL))
|
||||
elif user.posts.count() >= settings.FORUM_STAR_4_HALF:
|
||||
return mark_safe('<img src="%sforum/img/stars/Star_4_Half.gif" alt="" >' % (settings.MEDIA_URL))
|
||||
elif user.posts.count() >= settings.FORUM_STAR_4:
|
||||
return mark_safe('<img src="%sforum/img/stars/Star_4.gif" alt="" >' % (settings.MEDIA_URL))
|
||||
elif user.posts.count() >= settings.FORUM_STAR_3_HALF:
|
||||
return mark_safe('<img src="%sforum/img/stars/Star_3_Half.gif" alt="" >' % (settings.MEDIA_URL))
|
||||
elif user.posts.count() >= settings.FORUM_STAR_3:
|
||||
return mark_safe('<img src="%sforum/img/stars/Star_3.gif" alt="" >' % (settings.MEDIA_URL))
|
||||
elif user.posts.count() >= settings.FORUM_STAR_2_HALF:
|
||||
return mark_safe('<img src="%sforum/img/stars/Star_2_Half.gif" alt="" >' % (settings.MEDIA_URL))
|
||||
elif user.posts.count() >= settings.FORUM_STAR_2:
|
||||
return mark_safe('<img src="%sforum/img/stars/Star_2.gif" alt="" >' % (settings.MEDIA_URL))
|
||||
elif user.posts.count() >= settings.FORUM_STAR_1_HALF:
|
||||
return mark_safe('<img src="%sforum/img/stars/Star_1_Half.gif" alt="" >' % (settings.MEDIA_URL))
|
||||
elif user.posts.count() >= settings.FORUM_STAR_1:
|
||||
return mark_safe('<img src="%sforum/img/stars/Star_1.gif" alt="" >' % (settings.MEDIA_URL))
|
||||
elif user.posts.count() >= settings.FORUM_STAR_0_HALF:
|
||||
return mark_safe('<img src="%sforum/img/stars/Star_0_Half.gif" alt="" >' % (settings.MEDIA_URL))
|
||||
else:
|
||||
return mark_safe('<img src="%sforum/img/stars/Star_0.gif" alt="" >' % (settings.MEDIA_URL))
|
||||
|
||||
@register.filter
|
||||
def online(user):
|
||||
return cache.get(str(user.id))
|
||||
|
||||
@register.filter
|
||||
def pm_unreads(user):
|
||||
return PrivateMessage.objects.filter(dst_user=user, read=False).count()
|
||||
|
||||
@register.simple_tag
|
||||
def new_reports():
|
||||
return Report.objects.filter(zapped=False).count()
|
14
apps/forum/tests/__init__.py
Normal file
14
apps/forum/tests/__init__.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
"""
|
||||
The root of forum tests.
|
||||
"""
|
||||
import unittest
|
||||
|
||||
from apps.forum.tests.postmarkup import PostmarkupTestCase
|
||||
|
||||
def suite():
|
||||
cases = (PostmarkupTestCase,
|
||||
)
|
||||
tests = unittest.TestSuite(
|
||||
unittest.TestLoader().loadTestsFromTestCase(x)\
|
||||
for x in cases)
|
||||
return tests
|
26
apps/forum/tests/postmarkup.py
Normal file
26
apps/forum/tests/postmarkup.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
import unittest
|
||||
|
||||
from apps.forum.markups import mypostmarkup
|
||||
|
||||
class PostmarkupTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.markup = mypostmarkup.markup
|
||||
|
||||
def testLinkTag(self):
|
||||
link = 'http://ya.ru/'
|
||||
self.assertEqual('<a href="%s">%s</a>' % (link, link),
|
||||
self.markup('[url]%s[/url]' % link))
|
||||
|
||||
|
||||
def testPlainTest(self):
|
||||
text = 'just a text'
|
||||
self.assertEqual(text, self.markup(text))
|
||||
|
||||
def testNewLines(self):
|
||||
text = 'just a\n text'
|
||||
self.assertEqual('just a<br/> text', self.markup(text))
|
||||
|
||||
def testCodeTag(self):
|
||||
text = 'foo [code]foo\nbar[/code] bar'
|
||||
self.assertEqual('foo <div class="code"><pre>foo\nbar</pre></div>bar', self.markup(text))
|
||||
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue