This commit is contained in:
slav0nic 2009-01-05 14:30:08 +02:00
commit 2c2812c47c
279 changed files with 17212 additions and 0 deletions

0
__init__.py Normal file
View file

0
apps/__init__.py Normal file
View file

0
apps/account/__init__.py Normal file
View file

65
apps/account/auth_key.py Normal file
View 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
View 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
View file

View 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
View file

5
apps/account/settings.py Normal file
View 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)

View 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>&nbsp;&nbsp;
<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 %}

View 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 %}

View 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 %}

View 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 %}

View 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 %}.

View file

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block content %}
<h1>{% trans "Important information" %}</h1>
<p>{{ message }}</p>
{% endblock %}

View 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 %}

View 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 %}

View 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
View 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
View 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
View 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
View 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
View file

BIN
apps/captcha/data/Vera.ttf Normal file

Binary file not shown.

70
apps/captcha/fields.py Normal file
View 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
View 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
View 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
View 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
View 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
View file

47
apps/forum/admin.py Normal file
View 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
View 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
View 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
View 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

View file

View 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()

Binary file not shown.

View 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 "Контакты"

View file

View 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'

View 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()

View file

View 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')

File diff suppressed because it is too large Load diff

9
apps/forum/middleware.py Normal file
View 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)

View 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()

View 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()

View file

377
apps/forum/models.py Normal file
View 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)

View 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)

View 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>

View 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>
&nbsp;&nbsp;&nbsp;<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 %}

View 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 %}

View 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>&nbsp;<a href="http://python.su/forum/search.php?search_id=458844699&amp;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">оставил&nbsp;andr0s</span></strong>&nbsp; <span class="newtext">[&nbsp;<a href="http://python.su/forum/viewtopic.php?id=3282&amp;action=new" title="Перейти к первому новому сообщению в этом топике.">Новые&nbsp;сообщения</a>&nbsp;]</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> оставил&nbsp;andr0s</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="linksb">
<div class="inbox">
<p class="pagelink">Страниц: <strong>1</strong>&nbsp;<a href="http://python.su/forum/search.php?search_id=458844699&amp;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&amp;user_id=2">Показать Ваши сообщения</a></dd>
</dl>
{% endblock %}

View 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>&raquo; {% link forum %}</li></ul>
{% else %}
<ul><li><a href="{% url index %}">{% trans "Root" %}</a> </li><li>&raquo; {% link topic.forum %}</li><li>&raquo; {{ 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 %}

View 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>

View 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>&raquo; {% link topic.forum %} </li><li>&raquo; {{ 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 }}&nbsp;</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>&nbsp;&nbsp;<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 %}

View 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>&raquo; {% link post.topic.forum %}</li><li>&raquo; {{ 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 %}

View file

@ -0,0 +1 @@
{{ obj.body_html|safe }}

View file

@ -0,0 +1 @@
{{ obj.topic.forum.category|safe }} :: {{ obj.topic.forum|safe }} :: {{ obj.topic|safe }}

View file

@ -0,0 +1 @@
{{ obj.head.body_html|safe }}

View file

@ -0,0 +1 @@
{{ obj.forum.category|safe }} :: {{ obj.forum|safe }} :: {{ obj|safe }}

View 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>&raquo; {{ 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>&raquo; {{ 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 %}

View 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>

View 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 %}

View 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&amp;user_id={{ request.user.id }}">{% trans "Show your posts" %}</a></dd>
{% endif %}
</dl>
{% endblock %}

View 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 %}

View 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>&raquo; {{ 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" %}" />&nbsp;&nbsp;
<input type="submit" name="delete_topics" value="{% trans "Delete" %}" />&nbsp;&nbsp;
<input type="submit" name="open_topics" value="{% trans "Open" %}" />&nbsp;&nbsp;
<input type="submit" name="close_topics" value="{% trans "Close" %}" />
</p>
<div class="clearer"></div>
</div>
</div>
</form>
{% endblock %}
{% block controls %}
{% endblock %}

View 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 %}

View 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 %}

View 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 }}#}">&laquo;</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 }}#}">&raquo;</a>
{% endif %}
{% endifnotequal %}

View 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 %}

View 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 %}

View 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 %}

View 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>

View 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>&nbsp;&nbsp;
{% ifequal post_user.forum_profile.privacy_permission 0 %}
<a href="mailto:{{ post_user.email }}">{% trans "E-mail" %}</a>&nbsp;&nbsp;
{% endifequal %}
{% if user_is_authenticated %}
<a href="{% url forum_create_pm %}?recipient={{ post_user.username }}">{% trans "PM" %}</a>&nbsp;&nbsp;</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 %}

View 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 %}

View 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">&nbsp;&nbsp;<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 %}

View 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 %}

View 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 %}

View 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>

View 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 %}

View 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 }}&nbsp;&nbsp;(<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 %}

View 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>
&nbsp;&nbsp;&nbsp;<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 %}

View 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 %}

View 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 %}

View 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 }}&nbsp;&nbsp;<strong>[+{{ profile.reply_count_plus }} / -{{ profile.reply_count_minus }}] &nbsp;</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 %}

View 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 %}

View 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 %}

View 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 %}&nbsp;&raquo;&nbsp;{% link post.topic %}&nbsp;&raquo;&nbsp;<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&amp;user_id={{ request.user.id }}">{% trans "Show your posts" %}</a></dd>
{% endif %}
</dl>
{% endblock %}

View 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&amp;user_id={{ request.user.id }}">{% trans "Show your posts" %}</a></dd>
{% endif %}
</dl>
{% endblock %}

View 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>&raquo; {% link topic.forum %} </li><li>&raquo; {{ 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 }}&nbsp;</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>&nbsp;&nbsp;<strong>{{ post.user.forum_profile.reply_total }}&nbsp;&nbsp;</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>&nbsp;&nbsp;
{% ifequal post.user.forum_profile.privacy_permission 0 %}
<a href="mailto:{{ post.user.email }}">{% trans "E-mail" %}</a>&nbsp;&nbsp;
{% else %}
{% ifequal post.user.forum_profile.privacy_permission 1 %}
<a href="{% url misc %}?mail_to={{ post.user }}">{% trans "Send e-mail" %}</a>&nbsp;&nbsp;
{% endifequal %}
{% endifequal %}
{% if user.is_authenticated %}
<a href="{% url forum_create_pm %}?recipient={{ post.user.username }}">{% trans "PM" %}</a>&nbsp;&nbsp;</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>&raquo; {% link topic.forum %} </li><li>&raquo; {{ 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 %}

View 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 %}

View 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 %}

View 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 %}

View file

View 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()

View 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

View 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