summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README4
-rw-r--r--django_authopenid/forms.py3
-rw-r--r--[-rwxr-xr-x]django_authopenid/urls.py0
-rw-r--r--[-rwxr-xr-x]django_authopenid/views.py21
-rw-r--r--[-rwxr-xr-x]fbconnect/__init__.py0
-rw-r--r--[-rwxr-xr-x]fbconnect/fb.py16
-rw-r--r--[-rwxr-xr-x]fbconnect/forms.py0
-rw-r--r--[-rwxr-xr-x]fbconnect/models.py0
-rw-r--r--[-rwxr-xr-x]fbconnect/pjson.py0
-rw-r--r--[-rwxr-xr-x]fbconnect/tests.py0
-rw-r--r--[-rwxr-xr-x]fbconnect/urls.py10
-rw-r--r--[-rwxr-xr-x]fbconnect/views.py0
-rw-r--r--[-rwxr-xr-x]forum/__init__.py0
-rw-r--r--[-rwxr-xr-x]forum/admin.py3
-rw-r--r--[-rwxr-xr-x]forum/auth.py165
-rw-r--r--[-rwxr-xr-x]forum/badges/__init__.py0
-rw-r--r--[-rwxr-xr-x]forum/badges/base.py0
-rw-r--r--forum/conf/README5
-rw-r--r--forum/conf/__init__.py16
-rw-r--r--forum/conf/email.py51
-rw-r--r--forum/conf/external_keys.py93
-rw-r--r--forum/conf/flatpages.py37
-rw-r--r--forum/conf/forum_data_rules.py62
-rw-r--r--forum/conf/minimum_reputation.py148
-rw-r--r--forum/conf/reputation_changes.py149
-rw-r--r--forum/conf/settings_wrapper.py70
-rw-r--r--forum/conf/site_settings.py95
-rw-r--r--forum/conf/skin_counter_settings.py251
-rw-r--r--forum/conf/skin_general_settings.py38
-rw-r--r--forum/conf/user_settings.py30
-rw-r--r--forum/conf/vote_rules.py69
-rw-r--r--[-rwxr-xr-x]forum/const/__init__.py (renamed from forum/const.py)19
-rw-r--r--forum/const/message_keys.py19
-rw-r--r--forum/context.py30
-rw-r--r--forum/doc/HOW_TO_DEBUG (renamed from forum/documentation/HOW_TO_DEBUG)0
-rw-r--r--forum/doc/INSTALL (renamed from forum/documentation/INSTALL)0
-rw-r--r--forum/doc/INSTALL.pip (renamed from forum/documentation/INSTALL.pip)0
-rw-r--r--forum/doc/INSTALL.webfaction (renamed from forum/documentation/INSTALL.webfaction)0
-rw-r--r--forum/doc/ROADMAP.rst (renamed from forum/documentation/ROADMAP.rst)0
-rw-r--r--forum/doc/TODO.rst (renamed from forum/documentation/TODO.rst)0
-rw-r--r--forum/doc/UPGRADE (renamed from forum/documentation/UPGRADE)0
-rw-r--r--forum/doc/WISH_LIST (renamed from forum/documentation/WISH_LIST)0
-rw-r--r--forum/doc/askbot-requirements.txt (renamed from forum/documentation/askbot-requirements.txt)0
-rw-r--r--forum/doc/scratch (renamed from forum/documentation/scratch)5
-rw-r--r--[-rwxr-xr-x]forum/feed.py12
-rw-r--r--[-rwxr-xr-x]forum/forms.py20
-rw-r--r--forum/importers/stackexchange/management/commands/load_stackexchange.py4
-rw-r--r--[-rwxr-xr-x]forum/management/__init__.py0
-rw-r--r--[-rwxr-xr-x]forum/management/commands/__init__.py0
-rw-r--r--[-rwxr-xr-x]forum/management/commands/base_command.py0
-rw-r--r--[-rwxr-xr-x]forum/management/commands/clean_award_badges.py0
-rw-r--r--[-rwxr-xr-x]forum/management/commands/message_to_everyone.py0
-rw-r--r--[-rwxr-xr-x]forum/management/commands/multi_award_badges.py0
-rw-r--r--[-rwxr-xr-x]forum/management/commands/once_award_badges.py0
-rw-r--r--[-rwxr-xr-x]forum/management/commands/pg_base_command.py0
-rw-r--r--[-rwxr-xr-x]forum/management/commands/pg_clean_award_badges.py0
-rw-r--r--[-rwxr-xr-x]forum/management/commands/pg_multi_award_badges.py0
-rw-r--r--[-rwxr-xr-x]forum/management/commands/pg_once_award_badges.py0
-rw-r--r--[-rwxr-xr-x]forum/management/commands/sample_command.py0
-rw-r--r--[-rwxr-xr-x]forum/management/commands/send_email_alerts.py21
-rw-r--r--[-rwxr-xr-x]forum/management/commands/subscribe_everyone.py0
-rw-r--r--[-rwxr-xr-x]forum/middleware/__init__.py0
-rw-r--r--[-rwxr-xr-x]forum/middleware/anon_user.py5
-rw-r--r--[-rwxr-xr-x]forum/middleware/cancel.py0
-rw-r--r--[-rwxr-xr-x]forum/middleware/pagesize.py0
-rw-r--r--[-rwxr-xr-x]forum/models/__init__.py6
-rw-r--r--[-rwxr-xr-x]forum/models/answer.py0
-rw-r--r--[-rwxr-xr-x]forum/models/base.py0
-rw-r--r--[-rwxr-xr-x]forum/models/meta.py0
-rw-r--r--[-rwxr-xr-x]forum/models/question.py13
-rw-r--r--[-rwxr-xr-x]forum/models/repute.py0
-rw-r--r--[-rwxr-xr-x]forum/models/tag.py0
-rw-r--r--[-rwxr-xr-x]forum/models/user.py0
-rw-r--r--[-rwxr-xr-x]forum/modules.py0
-rw-r--r--forum/search/state_manager.py3
-rw-r--r--[-rwxr-xr-x]forum/settings.py22
-rw-r--r--[-rwxr-xr-x]forum/sitemap.py0
-rw-r--r--[-rwxr-xr-x]forum/skins/__init__.py49
-rw-r--r--forum/skins/default/templates/about.html20
-rw-r--r--forum/skins/default/templates/ask.html4
-rw-r--r--forum/skins/default/templates/ask_form.html4
-rw-r--r--forum/skins/default/templates/base.html4
-rw-r--r--forum/skins/default/templates/base_content.html4
-rw-r--r--forum/skins/default/templates/faq.html4
-rwxr-xr-xforum/skins/default/templates/fbconnect/xd_receiver.html2
-rw-r--r--forum/skins/default/templates/footer.html2
-rw-r--r--forum/skins/default/templates/header.html75
-rw-r--r--forum/skins/default/templates/privacy.html27
-rw-r--r--forum/skins/default/templates/question.html6
-rw-r--r--[-rwxr-xr-x]forum/templatetags/__init__.py0
-rw-r--r--[-rwxr-xr-x]forum/templatetags/extra_filters.py0
-rw-r--r--[-rwxr-xr-x]forum/templatetags/extra_tags.py60
-rw-r--r--[-rwxr-xr-x]forum/templatetags/smart_if.py0
-rw-r--r--[-rwxr-xr-x]forum/urls.py0
-rw-r--r--[-rwxr-xr-x]forum/user_messages/__init__.py0
-rw-r--r--[-rwxr-xr-x]forum/user_messages/context_processors.py0
-rw-r--r--[-rwxr-xr-x]forum/utils/__init__.py0
-rw-r--r--[-rwxr-xr-x]forum/utils/cache.py0
-rw-r--r--[-rwxr-xr-x]forum/utils/decorators.py0
-rw-r--r--[-rwxr-xr-x]forum/utils/diff.py0
-rw-r--r--[-rwxr-xr-x]forum/utils/email.py0
-rw-r--r--[-rwxr-xr-x]forum/utils/forms.py3
-rw-r--r--[-rwxr-xr-x]forum/utils/html.py0
-rw-r--r--[-rwxr-xr-x]forum/utils/lists.py0
-rw-r--r--[-rwxr-xr-x]forum/utils/odict.py0
-rw-r--r--[-rwxr-xr-x]forum/utils/time.py0
-rw-r--r--[-rwxr-xr-x]forum/views/__init__.py0
-rw-r--r--[-rwxr-xr-x]forum/views/commands.py20
-rw-r--r--[-rwxr-xr-x]forum/views/meta.py0
-rw-r--r--forum/views/readers.py3
-rw-r--r--[-rwxr-xr-x]forum/views/users.py7
-rw-r--r--[-rwxr-xr-x]forum/views/writers.py1
-rw-r--r--[-rwxr-xr-x]forum_modules/__init__.py0
-rw-r--r--[-rwxr-xr-x]forum_modules/authentication/auth.py0
-rw-r--r--[-rwxr-xr-x]forum_modules/books/__init__.py0
-rw-r--r--[-rwxr-xr-x]forum_modules/books/models.py0
-rw-r--r--[-rwxr-xr-x]forum_modules/books/urls.py0
-rw-r--r--[-rwxr-xr-x]forum_modules/books/views.py0
-rw-r--r--[-rwxr-xr-x]forum_modules/robotstxt/__init__.py0
-rw-r--r--[-rwxr-xr-x]forum_modules/robotstxt/urls.py0
-rw-r--r--keyedcache/__init__.py329
-rw-r--r--keyedcache/locale/de/LC_MESSAGES/django.mobin0 -> 445 bytes
-rw-r--r--keyedcache/locale/de/LC_MESSAGES/django.po40
-rw-r--r--keyedcache/locale/en/LC_MESSAGES/django.mobin0 -> 367 bytes
-rw-r--r--keyedcache/locale/en/LC_MESSAGES/django.po40
-rw-r--r--keyedcache/locale/es/LC_MESSAGES/django.po0
-rw-r--r--keyedcache/locale/fr/LC_MESSAGES/django.mobin0 -> 921 bytes
-rw-r--r--keyedcache/locale/fr/LC_MESSAGES/django.po69
-rw-r--r--keyedcache/locale/he/LC_MESSAGES/django.mobin0 -> 1001 bytes
-rw-r--r--keyedcache/locale/he/LC_MESSAGES/django.po60
-rw-r--r--keyedcache/locale/it/LC_MESSAGES/django.mobin0 -> 899 bytes
-rw-r--r--keyedcache/locale/it/LC_MESSAGES/django.po68
-rw-r--r--keyedcache/locale/ko/LC_MESSAGES/django.mobin0 -> 579 bytes
-rw-r--r--keyedcache/locale/ko/LC_MESSAGES/django.po40
-rw-r--r--keyedcache/locale/pl/LC_MESSAGES/django.mobin0 -> 888 bytes
-rw-r--r--keyedcache/locale/pl/LC_MESSAGES/django.po60
-rw-r--r--keyedcache/locale/pt_BR/LC_MESSAGES/django.mobin0 -> 778 bytes
-rw-r--r--keyedcache/locale/pt_BR/LC_MESSAGES/django.po61
-rw-r--r--keyedcache/locale/ru/LC_MESSAGES/django.mobin0 -> 575 bytes
-rw-r--r--keyedcache/locale/ru/LC_MESSAGES/django.po40
-rw-r--r--keyedcache/locale/sv/LC_MESSAGES/django.mobin0 -> 683 bytes
-rw-r--r--keyedcache/locale/sv/LC_MESSAGES/django.po44
-rw-r--r--keyedcache/locale/tr/LC_MESSAGES/django.mobin0 -> 660 bytes
-rw-r--r--keyedcache/locale/tr/LC_MESSAGES/django.po42
-rw-r--r--keyedcache/models.py86
-rw-r--r--keyedcache/templates/keyedcache/delete.html21
-rw-r--r--keyedcache/templates/keyedcache/stats.html21
-rw-r--r--keyedcache/templates/keyedcache/view.html18
-rw-r--r--keyedcache/tests.py150
-rw-r--r--keyedcache/threaded.py32
-rw-r--r--keyedcache/urls.py10
-rw-r--r--keyedcache/utils.py14
-rw-r--r--keyedcache/views.py103
-rw-r--r--livesettings/__init__.py16
-rw-r--r--livesettings/forms.py38
-rw-r--r--livesettings/functions.py247
-rw-r--r--livesettings/locale/de/LC_MESSAGES/django.mobin0 -> 706 bytes
-rw-r--r--livesettings/locale/de/LC_MESSAGES/django.po101
-rw-r--r--livesettings/locale/en/LC_MESSAGES/django.mobin0 -> 367 bytes
-rw-r--r--livesettings/locale/en/LC_MESSAGES/django.po100
-rw-r--r--livesettings/locale/es/LC_MESSAGES/django.po0
-rw-r--r--livesettings/locale/fr/LC_MESSAGES/django.mobin0 -> 1621 bytes
-rw-r--r--livesettings/locale/fr/LC_MESSAGES/django.po113
-rw-r--r--livesettings/locale/he/LC_MESSAGES/django.mobin0 -> 1655 bytes
-rw-r--r--livesettings/locale/he/LC_MESSAGES/django.po98
-rw-r--r--livesettings/locale/it/LC_MESSAGES/django.mobin0 -> 1582 bytes
-rw-r--r--livesettings/locale/it/LC_MESSAGES/django.po106
-rw-r--r--livesettings/locale/ko/LC_MESSAGES/django.mobin0 -> 1128 bytes
-rw-r--r--livesettings/locale/ko/LC_MESSAGES/django.po100
-rw-r--r--livesettings/locale/pl/LC_MESSAGES/django.mobin0 -> 1470 bytes
-rw-r--r--livesettings/locale/pl/LC_MESSAGES/django.po97
-rw-r--r--livesettings/locale/pt_BR/LC_MESSAGES/django.mobin0 -> 1208 bytes
-rw-r--r--livesettings/locale/pt_BR/LC_MESSAGES/django.po100
-rw-r--r--livesettings/locale/ru/LC_MESSAGES/django.mobin0 -> 1178 bytes
-rw-r--r--livesettings/locale/ru/LC_MESSAGES/django.po85
-rw-r--r--livesettings/locale/sv/LC_MESSAGES/django.mobin0 -> 1481 bytes
-rw-r--r--livesettings/locale/sv/LC_MESSAGES/django.po92
-rw-r--r--livesettings/locale/tr/LC_MESSAGES/django.mobin0 -> 1242 bytes
-rw-r--r--livesettings/locale/tr/LC_MESSAGES/django.po102
-rw-r--r--livesettings/models.py170
-rw-r--r--livesettings/overrides.py55
-rw-r--r--livesettings/signals.py3
-rw-r--r--livesettings/templates/livesettings/_admin_site_views.html15
-rw-r--r--livesettings/templates/livesettings/group_settings.html81
-rw-r--r--livesettings/templates/livesettings/site_settings.html101
-rw-r--r--livesettings/templates/livesettings/text.txt1
-rw-r--r--livesettings/templatetags/__init__.py0
-rw-r--r--livesettings/templatetags/config_tags.py91
-rw-r--r--livesettings/tests.py545
-rw-r--r--livesettings/urls.py7
-rw-r--r--livesettings/utils.py87
-rw-r--r--livesettings/values.py628
-rw-r--r--livesettings/views.py93
-rw-r--r--settings.py17
-rwxr-xr-xsettings_local.py.dist100
-rw-r--r--urls.py7
196 files changed, 6133 insertions, 421 deletions
diff --git a/README b/README
index 56da61c5..dea68101 100644
--- a/README
+++ b/README
@@ -1,8 +1,8 @@
This is Askbot project - open source Q&A system
-Demo site is http://askbot.org/meta
+Demo site is http://askbot.org
-All documentation is in the directory forum/documentation
+All documentation is in the directory forum/doc
askbot-devel repository is open to anyone who wants to help develop Askbot - just drop us a note
diff --git a/django_authopenid/forms.py b/django_authopenid/forms.py
index 2f34986c..2fd3fd7f 100644
--- a/django_authopenid/forms.py
+++ b/django_authopenid/forms.py
@@ -35,6 +35,7 @@ from django.contrib.auth.models import User
from django.contrib.auth import authenticate
from django.utils.translation import ugettext as _
from django.conf import settings
+from forum.conf import settings as forum_settings
import types
import re
from django.utils.safestring import mark_safe
@@ -254,7 +255,7 @@ class ChangeEmailForm(forms.Form):
def clean_email(self):
""" check if email don't exist """
if 'email' in self.cleaned_data:
- if settings.EMAIL_UNIQUE == True:
+ if forum_settings.EMAIL_UNIQUE == True:
try:
user = User.objects.get(email = self.cleaned_data['email'])
if self.user and self.user == user:
diff --git a/django_authopenid/urls.py b/django_authopenid/urls.py
index 65afc45a..65afc45a 100755..100644
--- a/django_authopenid/urls.py
+++ b/django_authopenid/urls.py
diff --git a/django_authopenid/views.py b/django_authopenid/views.py
index 4f7d3efa..688a41fc 100755..100644
--- a/django_authopenid/views.py
+++ b/django_authopenid/views.py
@@ -35,6 +35,7 @@ from django.http import HttpResponseRedirect, get_host, Http404, \
from django.shortcuts import render_to_response
from django.template import RequestContext, loader, Context
from django.conf import settings
+from forum.conf import settings as forum_settings
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
from django.contrib.auth import authenticate
@@ -364,7 +365,7 @@ def signin(request,newquestion=False,newanswer=False):
'form2': form_signin,
'msg': request.GET.get('msg',''),
'sendpw_url': reverse('user_sendpw'),
- 'fb_api_key': settings.FB_API_KEY,
+ 'fb_api_key': forum_settings.FB_API_KEY,
}, context_instance=RequestContext(request))
def complete_signin(request):
@@ -507,7 +508,7 @@ def register(request):
#if user just logged in and did not need to create the new account
if user_ != None:
- if settings.EMAIL_VALIDATION == 'on':
+ if forum_settings.EMAIL_VALIDATION == True:
logging.debug('sending email validation')
send_new_email_key(user_,nomessage=True)
output = validation_email_sent(request)
@@ -616,7 +617,7 @@ def signup(request):
'authopenid/confirm_email.txt'
)
message_context = Context({
- 'signup_url': settings.APP_URL + reverse('user_signin'),
+ 'signup_url': forum_settings.APP_URL + reverse('user_signin'),
'username': username,
'password': password,
})
@@ -749,7 +750,7 @@ def set_new_email(user, new_email, nomessage=False):
user.email = new_email
user.email_isvalid = False
user.save()
- if settings.EMAIL_VALIDATION == 'on':
+ if forum_settings.EMAIL_VALIDATION == True:
send_new_email_key(user,nomessage=nomessage)
def _send_email_key(user):
@@ -760,7 +761,7 @@ def _send_email_key(user):
message_template = loader.get_template('authopenid/email_validation.txt')
import settings
message_context = Context({
- 'validation_link': settings.APP_URL + reverse('user_verifyemail', kwargs={'id':user.id,'key':user.email_key})
+ 'validation_link': forum_settings.APP_URL + reverse('user_verifyemail', kwargs={'id':user.id,'key':user.email_key})
})
message = message_template.render(message_context)
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [user.email])
@@ -787,7 +788,7 @@ def send_email_key(request):
authopenid/changeemail.html template
"""
- if settings.EMAIL_VALIDATION != 'off':
+ if forum_settings.EMAIL_VALIDATION == True:
if request.user.email_isvalid:
return render_to_response('authopenid/changeemail.html',
{ 'email': request.user.email,
@@ -817,7 +818,7 @@ def verifyemail(request,id=None,key=None):
url = /email/verify/{{user.id}}/{{user.email_key}}/
"""
logging.debug('')
- if settings.EMAIL_VALIDATION != 'off':
+ if forum.settings.EMAIL_VALIDATION == True:
user = User.objects.get(id=id)
if user:
if user.email_key == key:
@@ -854,7 +855,7 @@ def changeemail(request, action='change'):
if form.is_valid():
new_email = form.cleaned_data['email']
if new_email != user_.email:
- if settings.EMAIL_VALIDATION == 'on':
+ if forum_settings.EMAIL_VALIDATION == True:
action = 'validate'
else:
action = 'done_novalidate'
@@ -1118,10 +1119,10 @@ def sendpw(request):
subject = _("Request for new password")
message_template = loader.get_template(
'authopenid/sendpw_email.txt')
- key_link = settings.APP_URL + reverse('user_confirmchangepw') + '?key=' + confirm_key
+ key_link = forum_settings.APP_URL + reverse('user_confirmchangepw') + '?key=' + confirm_key
logging.debug('emailing new password for %s' % form.user_cache.username)
message_context = Context({
- 'site_url': settings.APP_URL + reverse('index'),
+ 'site_url': forum_settings.APP_URL + reverse('index'),
'key_link': key_link,
'username': form.user_cache.username,
'password': new_pw,
diff --git a/fbconnect/__init__.py b/fbconnect/__init__.py
index e69de29b..e69de29b 100755..100644
--- a/fbconnect/__init__.py
+++ b/fbconnect/__init__.py
diff --git a/fbconnect/fb.py b/fbconnect/fb.py
index afcd8210..8d41c3a2 100755..100644
--- a/fbconnect/fb.py
+++ b/fbconnect/fb.py
@@ -1,4 +1,4 @@
-from django.conf import settings
+from forum.conf import settings as forum_settings
from time import time
from datetime import datetime
from urllib import urlopen, urlencode
@@ -20,11 +20,11 @@ def generate_sig(values):
for key in sorted(values.keys()):
keys.append(key)
- signature = ''.join(['%s=%s' % (key, values[key]) for key in keys]) + settings.FB_SECRET
+ signature = ''.join(['%s=%s' % (key, values[key]) for key in keys]) + forum_settings.FB_SECRET
return hashlib.md5(signature).hexdigest()
def check_cookies_signature(cookies):
- API_KEY = settings.FB_API_KEY
+ API_KEY = forum_settings.FB_API_KEY
values = {}
@@ -37,10 +37,10 @@ def check_cookies_signature(cookies):
def get_user_data(cookies):
request_data = {
'method': 'Users.getInfo',
- 'api_key': settings.FB_API_KEY,
+ 'api_key': forum_settings.FB_API_KEY,
'call_id': time(),
'v': '1.0',
- 'uids': cookies[settings.FB_API_KEY + '_user'],
+ 'uids': cookies[forum_settings.FB_API_KEY + '_user'],
'fields': 'name,first_name,last_name',
'format': 'json',
}
@@ -52,7 +52,7 @@ def get_user_data(cookies):
def delete_cookies(response):
- API_KEY = settings.FB_API_KEY
+ API_KEY = forum_settings.FB_API_KEY
response.delete_cookie(API_KEY + '_user')
response.delete_cookie(API_KEY + '_session_key')
@@ -62,7 +62,7 @@ def delete_cookies(response):
response.delete_cookie('fbsetting_' + API_KEY)
def check_session_expiry(cookies):
- return datetime.fromtimestamp(float(cookies[settings.FB_API_KEY+'_expires'])) > datetime.now()
+ return datetime.fromtimestamp(float(cookies[forum_settings.FB_API_KEY+'_expires'])) > datetime.now()
STATES = {
'FIRSTTIMER': 1,
@@ -72,7 +72,7 @@ STATES = {
}
def get_user_state(request):
- API_KEY = settings.FB_API_KEY
+ API_KEY = forum_settings.FB_API_KEY
logging.debug('')
if API_KEY in request.COOKIES:
diff --git a/fbconnect/forms.py b/fbconnect/forms.py
index 94f86816..94f86816 100755..100644
--- a/fbconnect/forms.py
+++ b/fbconnect/forms.py
diff --git a/fbconnect/models.py b/fbconnect/models.py
index 2172217d..2172217d 100755..100644
--- a/fbconnect/models.py
+++ b/fbconnect/models.py
diff --git a/fbconnect/pjson.py b/fbconnect/pjson.py
index 273b684e..273b684e 100755..100644
--- a/fbconnect/pjson.py
+++ b/fbconnect/pjson.py
diff --git a/fbconnect/tests.py b/fbconnect/tests.py
index 2247054b..2247054b 100755..100644
--- a/fbconnect/tests.py
+++ b/fbconnect/tests.py
diff --git a/fbconnect/urls.py b/fbconnect/urls.py
index bf2d4364..81b0cb0f 100755..100644
--- a/fbconnect/urls.py
+++ b/fbconnect/urls.py
@@ -1,13 +1,15 @@
from django.conf.urls.defaults import *
from django.utils.translation import ugettext as _
from django.views.generic.simple import direct_to_template
-from django.conf import settings
from views import signin, register
urlpatterns = patterns('',
- url(r'^xd_receiver$', direct_to_template, {'template': 'fbconnect/xd_receiver.html',\
- 'extra_context': {'APP_SHORT_NAME':settings.APP_SHORT_NAME}},\
- name='xd_receiver'),
+ url(
+ r'^xd_receiver$',
+ direct_to_template,
+ {'template': 'fbconnect/xd_receiver.html',},
+ name='xd_receiver'
+ ),
url(r'^%s$' % _('signin/'), signin, name="fb_signin"),
url(r'^%s%s$' % (_('signin/'), _('newquestion/')), signin, {'newquestion': True}, name="fb_signin_new_question"),
diff --git a/fbconnect/views.py b/fbconnect/views.py
index 1781f6bf..1781f6bf 100755..100644
--- a/fbconnect/views.py
+++ b/fbconnect/views.py
diff --git a/forum/__init__.py b/forum/__init__.py
index 85cd5d26..85cd5d26 100755..100644
--- a/forum/__init__.py
+++ b/forum/__init__.py
diff --git a/forum/admin.py b/forum/admin.py
index 3afa2241..41b68b9a 100755..100644
--- a/forum/admin.py
+++ b/forum/admin.py
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
-
from django.contrib import admin
-from models import *
+from forum.models import *
class AnonymousQuestionAdmin(admin.ModelAdmin):
"""AnonymousQuestion admin class"""
diff --git a/forum/auth.py b/forum/auth.py
index 5d6e71c4..6c9998cb 100755..100644
--- a/forum/auth.py
+++ b/forum/auth.py
@@ -14,49 +14,7 @@ from models import mark_offensive, delete_post_or_answer
from const import TYPE_REPUTATION
import logging
-VOTE_UP = 15
-FLAG_OFFENSIVE = 15
-POST_IMAGES = 15
-LEAVE_COMMENTS = 50
-UPLOAD_FILES = 60
-VOTE_DOWN = 100
-CLOSE_OWN_QUESTIONS = 250
-RETAG_OTHER_QUESTIONS = 500
-REOPEN_OWN_QUESTIONS = 500
-EDIT_COMMUNITY_WIKI_POSTS = 750
-EDIT_OTHER_POSTS = 2000
-DELETE_COMMENTS = 2000
-VIEW_OFFENSIVE_FLAGS = 2000
-DISABLE_URL_NOFOLLOW = 2000
-CLOSE_OTHER_QUESTIONS = 3000
-LOCK_POSTS = 4000
-
-VOTE_RULES = {
- 'scope_votes_per_user_per_day' : 30, # how many votes of one user has everyday
- 'scope_flags_per_user_per_day' : 5, # how many times user can flag posts everyday
- 'scope_warn_votes_left' : 10, # start when to warn user how many votes left
- 'scope_deny_unvote_days' : 1, # if 1 days passed, user can't cancel votes.
- 'scope_flags_invisible_main_page' : 3, # post doesn't show on main page if has more than 3 offensive flags
- 'scope_flags_delete_post' : 5, # post will be deleted if it has more than 5 offensive flags
-}
-
-REPUTATION_RULES = {
- 'initial_score' : 1,
- 'scope_per_day_by_upvotes' : 200,
- 'gain_by_upvoted' : 10,
- 'gain_by_answer_accepted' : 15,
- 'gain_by_accepting_answer' : 2,
- 'gain_by_downvote_canceled' : 2,
- 'gain_by_canceling_downvote' : 1,
- 'lose_by_canceling_accepted_answer' : -2,
- 'lose_by_accepted_answer_cancled' : -15,
- 'lose_by_downvoted' : -2,
- 'lose_by_flagged' : -2,
- 'lose_by_downvoting' : -1,
- 'lose_by_flagged_lastrevision_3_times': -30,
- 'lose_by_flagged_lastrevision_5_times': -100,
- 'lose_by_upvote_canceled' : -10,
-}
+from forum.conf import settings as forum_settings
def can_moderate_users(user):
return user.is_superuser
@@ -64,13 +22,13 @@ def can_moderate_users(user):
def can_vote_up(user):
"""Determines if a User can vote Questions and Answers up."""
return user.is_authenticated() and (
- user.reputation >= VOTE_UP or
+ user.reputation >= forum_settings.MIN_REP_TO_VOTE_UP or
user.is_superuser)
def can_flag_offensive(user):
"""Determines if a User can flag Questions and Answers as offensive."""
return user.is_authenticated() and (
- user.reputation >= FLAG_OFFENSIVE or
+ user.reputation >= forum_settings.MIN_REP_TO_FLAG_OFFENSIVE or
user.is_superuser)
def can_add_comments(user,subject):
@@ -78,7 +36,7 @@ def can_add_comments(user,subject):
if user.is_authenticated():
if user.id == subject.author.id:
return True
- if user.reputation >= LEAVE_COMMENTS:
+ if user.reputation >= forum_settings.MIN_REP_TO_LEAVE_COMMENTS:
return True
if user.is_superuser:
return True
@@ -89,53 +47,55 @@ def can_add_comments(user,subject):
def can_vote_down(user):
"""Determines if a User can vote Questions and Answers down."""
return user.is_authenticated() and (
- user.reputation >= VOTE_DOWN or
+ user.reputation >= forum_settings.MIN_REP_TO_VOTE_DOWN or
user.is_superuser)
def can_retag_questions(user):
"""Determines if a User can retag Questions."""
return user.is_authenticated() and (
- RETAG_OTHER_QUESTIONS <= user.reputation < EDIT_OTHER_POSTS or
+ forum_settings.MIN_REP_TO_RETAG_OTHERS_QUESTIONS
+ <= user.reputation
+ < forum_settings.MIN_REP_TO_EDIT_OTHERS_POSTS or
user.is_superuser)
def can_edit_post(user, post):
"""Determines if a User can edit the given Question or Answer."""
return user.is_authenticated() and (
user.id == post.author_id or
- (post.wiki and user.reputation >= EDIT_COMMUNITY_WIKI_POSTS) or
- user.reputation >= EDIT_OTHER_POSTS or
+ (post.wiki and user.reputation >= forum_settings.MIN_REP_TO_EDIT_WIKI) or
+ user.reputation >= forum_settings.MIN_REP_TO_EDIT_OTHERS_POSTS or
user.is_superuser)
def can_delete_comment(user, comment):
"""Determines if a User can delete the given Comment."""
return user.is_authenticated() and (
user.id == comment.user_id or
- user.reputation >= DELETE_COMMENTS or
+ user.reputation >= forum_settings.MIN_REP_TO_DELETE_OTHERS_COMMENTS or
user.is_superuser)
def can_view_offensive_flags(user):
"""Determines if a User can view offensive flag counts."""
return user.is_authenticated() and (
- user.reputation >= VIEW_OFFENSIVE_FLAGS or
+ user.reputation >= forum_settings.MIN_REP_TO_VIEW_OFFENSIVE_FLAGS or
user.is_superuser)
def can_close_question(user, question):
"""Determines if a User can close the given Question."""
return user.is_authenticated() and (
(user.id == question.author_id and
- user.reputation >= CLOSE_OWN_QUESTIONS) or
- user.reputation >= CLOSE_OTHER_QUESTIONS or
+ user.reputation >= forum_settings.MIN_REP_TO_CLOSE_OWN_QUESTIONS) or
+ user.reputation >= forum_settings.MIN_REP_TO_CLOSE_OTHERS_QUESTIONS or
user.is_superuser)
def can_lock_posts(user):
"""Determines if a User can lock Questions or Answers."""
return user.is_authenticated() and (
- user.reputation >= LOCK_POSTS or
+ user.reputation >= forum_settings.MIN_REP_TO_LOCK_POSTS or
user.is_superuser)
def can_follow_url(user):
"""Determines if the URL link can be followed by Google search engine."""
- return user.reputation >= DISABLE_URL_NOFOLLOW
+ return user.reputation >= forum_settings.MIN_REP_TO_DISABLE_URL_NOFOLLOW
def can_accept_answer(user, question, answer):
return (user.is_authenticated() and
@@ -146,7 +106,7 @@ def can_accept_answer(user, question, answer):
def can_reopen_question(user, question):
return (user.is_authenticated() and
user.id == question.author_id and
- user.reputation >= REOPEN_OWN_QUESTIONS) or user.is_superuser
+ user.reputation >= forum_settings.MIN_REP_TO_REOPEN_OWN_QUESTIONS) or user.is_superuser
def can_delete_post(user, post):
if user.is_superuser:
@@ -182,7 +142,8 @@ def can_view_user_edit(request_user, target_user):
return (request_user.is_authenticated() and request_user == target_user)
def can_upload_files(request_user):
- return (request_user.is_authenticated() and request_user.reputation >= UPLOAD_FILES) or \
+ return (request_user.is_authenticated() and
+ request_user.reputation >= forum_settings.MIN_REP_TO_UPLOAD_FILES) or \
request_user.is_superuser
###########################################
@@ -205,7 +166,7 @@ def onFlaggedItem(item, post, user, timestamp=None):
post.save()
post.author.reputation = calculate_reputation(post.author.reputation,
- int(REPUTATION_RULES['lose_by_flagged']))
+ forum_settings.REP_LOSS_FOR_RECEIVING_DOWNVOTE)
post.author.save()
question = post
@@ -213,37 +174,39 @@ def onFlaggedItem(item, post, user, timestamp=None):
question = post.question
reputation = Repute(user=post.author,
- negative=int(REPUTATION_RULES['lose_by_flagged']),
+ negative=forum_settings.REP_LOSS_FOR_RECEIVING_DOWNVOTE,
question=question, reputed_at=timestamp,
reputation_type=-4,
reputation=post.author.reputation)
reputation.save()
#todo: These should be updated to work on same revisions.
- if post.offensive_flag_count == VOTE_RULES['scope_flags_invisible_main_page'] :
+ if post.offensive_flag_count == forum_settings.MIN_FLAGS_TO_HIDE_POST:
post.author.reputation = calculate_reputation(post.author.reputation,
- int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']))
+ forum_settings.REP_LOSS_FOR_RECEIVING_THREE_FLAGS_PER_REVISION)
+
post.author.save()
reputation = Repute(user=post.author,
- negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']),
- question=question,
- reputed_at=timestamp,
- reputation_type=-6,
- reputation=post.author.reputation)
+ negative=forum_settings.REP_LOSS_FOR_RECEIVING_THREE_FLAGS_PER_REVISION,
+ question=question,
+ reputed_at=timestamp,
+ reputation_type=-6,
+ reputation=post.author.reputation)
reputation.save()
- elif post.offensive_flag_count == VOTE_RULES['scope_flags_delete_post']:
+ elif post.offensive_flag_count == forum_settings.MIN_FLAGS_TO_DELETE_POST:
post.author.reputation = calculate_reputation(post.author.reputation,
- int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']))
+ forum_settings.REP_LOSS_FOR_RECEIVING_FIVE_FLAGS_PER_REVISION)
+
post.author.save()
reputation = Repute(user=post.author,
- negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']),
- question=question,
- reputed_at=timestamp,
- reputation_type=-7,
- reputation=post.author.reputation)
+ negative=forum_settings.REP_LOSS_FOR_RECEIVING_FIVE_FLAGS_PER_REVISION,
+ question=question,
+ reputed_at=timestamp,
+ reputation_type=-7,
+ reputation=post.author.reputation)
reputation.save()
post.deleted = True
@@ -267,11 +230,13 @@ def onAnswerAccept(answer, user, timestamp=None):
answer.save()
answer.question.save()
- answer.author.reputation = calculate_reputation(answer.author.reputation,
- int(REPUTATION_RULES['gain_by_answer_accepted']))
+ answer.author.reputation = calculate_reputation(
+ answer.author.reputation,
+ forum_settings.REP_GAIN_FOR_RECEIVING_ANSWER_ACCEPTANCE
+ )
answer.author.save()
reputation = Repute(user=answer.author,
- positive=int(REPUTATION_RULES['gain_by_answer_accepted']),
+ positive=REP_GAIN_FOR_RECEIVING_ANSWER_ACCEPTANCE,
question=answer.question,
reputed_at=timestamp,
reputation_type=2,
@@ -279,10 +244,10 @@ def onAnswerAccept(answer, user, timestamp=None):
reputation.save()
user.reputation = calculate_reputation(user.reputation,
- int(REPUTATION_RULES['gain_by_accepting_answer']))
+ forum_settings.REP_GAIN_FOR_ACCEPTING_ANSWER)
user.save()
reputation = Repute(user=user,
- positive=int(REPUTATION_RULES['gain_by_accepting_answer']),
+ positive=forum_settings.REP_GAIN_FOR_ACCEPTING_ANSWER,
question=answer.question,
reputed_at=timestamp,
reputation_type=3,
@@ -300,21 +265,21 @@ def onAnswerAcceptCanceled(answer, user, timestamp=None):
answer.question.save()
answer.author.reputation = calculate_reputation(answer.author.reputation,
- int(REPUTATION_RULES['lose_by_accepted_answer_cancled']))
+ forum_settings.REP_LOSS_FOR_RECEIVING_CANCELATION_OF_ANSWER_ACCEPTANCE)
answer.author.save()
reputation = Repute(user=answer.author,
- negative=int(REPUTATION_RULES['lose_by_accepted_answer_cancled']),
- question=answer.question,
- reputed_at=timestamp,
- reputation_type=-2,
- reputation=answer.author.reputation)
+ negative=forum_settings.REP_LOSS_FOR_RECEIVING_CANCELATION_OF_ANSWER_ACCEPTANCE,
+ question=answer.question,
+ reputed_at=timestamp,
+ reputation_type=-2,
+ reputation=answer.author.reputation)
reputation.save()
user.reputation = calculate_reputation(user.reputation,
- int(REPUTATION_RULES['lose_by_canceling_accepted_answer']))
+ forum_settings.REP_LOSS_FOR_CANCELING_ANSWER_ACCEPTANCE)
user.save()
reputation = Repute(user=user,
- negative=int(REPUTATION_RULES['lose_by_canceling_accepted_answer']),
+ negative=forum_settings.REP_LOSS_FOR_CANCELING_ANSWER_ACCEPTANCE,
question=answer.question,
reputed_at=timestamp,
reputation_type=-1,
@@ -334,9 +299,9 @@ def onUpVoted(vote, post, user, timestamp=None):
if not post.wiki:
author = post.author
todays_rep_gain = Repute.objects.get_reputation_by_upvoted_today(author)
- if todays_rep_gain < int(REPUTATION_RULES['scope_per_day_by_upvotes']):
+ if todays_rep_gain < forum_settings.MAX_REP_GAIN_PER_USER_PER_DAY:
author.reputation = calculate_reputation(author.reputation,
- int(REPUTATION_RULES['gain_by_upvoted']))
+ forum_settings.REP_GAIN_FOR_RECEIVING_UPVOTE)
author.save()
question = post
@@ -344,7 +309,7 @@ def onUpVoted(vote, post, user, timestamp=None):
question = post.question
reputation = Repute(user=author,
- positive=int(REPUTATION_RULES['gain_by_upvoted']),
+ positive=forum_settings.REP_GAIN_FOR_RECEIVING_UPVOTE,
question=question,
reputed_at=timestamp,
reputation_type=1,
@@ -366,7 +331,7 @@ def onUpVotedCanceled(vote, post, user, timestamp=None):
if not post.wiki:
author = post.author
author.reputation = calculate_reputation(author.reputation,
- int(REPUTATION_RULES['lose_by_upvote_canceled']))
+ forum_settings.REP_LOSS_FOR_RECEIVING_UPVOTE_CANCELATION)
author.save()
question = post
@@ -374,7 +339,7 @@ def onUpVotedCanceled(vote, post, user, timestamp=None):
question = post.question
reputation = Repute(user=author,
- negative=int(REPUTATION_RULES['lose_by_upvote_canceled']),
+ negative=forum_settings.REP_LOSS_FOR_RECEIVING_UPVOTE_CANCELATION,
question=question,
reputed_at=timestamp,
reputation_type=-8,
@@ -394,7 +359,7 @@ def onDownVoted(vote, post, user, timestamp=None):
if not post.wiki:
author = post.author
author.reputation = calculate_reputation(author.reputation,
- int(REPUTATION_RULES['lose_by_downvoted']))
+ forum_settings.REP_LOSS_FOR_DOWNVOTING)
author.save()
question = post
@@ -402,7 +367,7 @@ def onDownVoted(vote, post, user, timestamp=None):
question = post.question
reputation = Repute(user=author,
- negative=int(REPUTATION_RULES['lose_by_downvoted']),
+ negative=forum_settings.REP_LOSS_FOR_DOWNVOTING,
question=question,
reputed_at=timestamp,
reputation_type=-3,
@@ -410,11 +375,11 @@ def onDownVoted(vote, post, user, timestamp=None):
reputation.save()
user.reputation = calculate_reputation(user.reputation,
- int(REPUTATION_RULES['lose_by_downvoting']))
+ forum_settings.REP_LOSS_FOR_RECEIVING_DOWNVOTE)
user.save()
reputation = Repute(user=user,
- negative=int(REPUTATION_RULES['lose_by_downvoting']),
+ negative=forum_settings.REP_LOSS_FOR_RECEIVING_DOWNVOTE,
question=question,
reputed_at=timestamp,
reputation_type=-5,
@@ -436,7 +401,7 @@ def onDownVotedCanceled(vote, post, user, timestamp=None):
if not post.wiki:
author = post.author
author.reputation = calculate_reputation(author.reputation,
- int(REPUTATION_RULES['gain_by_downvote_canceled']))
+ forum_settings.REP_GAIN_FOR_RECEIVING_DOWNVOTE_CANCELATION)
author.save()
question = post
@@ -444,7 +409,7 @@ def onDownVotedCanceled(vote, post, user, timestamp=None):
question = post.question
reputation = Repute(user=author,
- positive=int(REPUTATION_RULES['gain_by_downvote_canceled']),
+ positive=forum_settings.REP_GAIN_FOR_RECEIVING_DOWNVOTE_CANCELATION,
question=question,
reputed_at=timestamp,
reputation_type=4,
@@ -452,11 +417,11 @@ def onDownVotedCanceled(vote, post, user, timestamp=None):
reputation.save()
user.reputation = calculate_reputation(user.reputation,
- int(REPUTATION_RULES['gain_by_canceling_downvote']))
+ forum_settings.REP_GAIN_FOR_CANCELING_DOWNVOTE)
user.save()
reputation = Repute(user=user,
- positive=int(REPUTATION_RULES['gain_by_canceling_downvote']),
+ positive=forum_settings.REP_GAIN_FOR_CANCELING_DOWNVOTE,
question=question,
reputed_at=timestamp,
reputation_type=5,
diff --git a/forum/badges/__init__.py b/forum/badges/__init__.py
index 8d7cd097..8d7cd097 100755..100644
--- a/forum/badges/__init__.py
+++ b/forum/badges/__init__.py
diff --git a/forum/badges/base.py b/forum/badges/base.py
index 03ef3565..03ef3565 100755..100644
--- a/forum/badges/base.py
+++ b/forum/badges/base.py
diff --git a/forum/conf/README b/forum/conf/README
new file mode 100644
index 00000000..4dd62329
--- /dev/null
+++ b/forum/conf/README
@@ -0,0 +1,5 @@
+this directory contains
+forum site configurations for livesettings
+
+they need to be imported in models so made this a part of
+models module
diff --git a/forum/conf/__init__.py b/forum/conf/__init__.py
new file mode 100644
index 00000000..54ea2e32
--- /dev/null
+++ b/forum/conf/__init__.py
@@ -0,0 +1,16 @@
+#import these to compile code and install values
+import forum.conf.minimum_reputation
+import forum.conf.vote_rules
+import forum.conf.reputation_changes
+import forum.conf.email
+import forum.conf.forum_data_rules
+import forum.conf.flatpages
+import forum.conf.site_settings
+import forum.conf.external_keys
+import forum.conf.skin_counter_settings
+import forum.conf.skin_general_settings
+import forum.conf.user_settings
+
+#import main settings object
+from forum.conf.settings_wrapper import settings
+
diff --git a/forum/conf/email.py b/forum/conf/email.py
new file mode 100644
index 00000000..8b0f2af3
--- /dev/null
+++ b/forum/conf/email.py
@@ -0,0 +1,51 @@
+"""
+Email related settings
+"""
+from forum.conf.settings_wrapper import settings
+from livesettings import ConfigurationGroup, IntegerValue, BooleanValue
+from livesettings import StringValue
+from django.utils.translation import ugettext as _
+
+EMAIL = ConfigurationGroup(
+ 'EMAIL',
+ _('Email and email alert settings'),
+ )
+
+settings.register(
+ IntegerValue(
+ EMAIL,
+ 'MAX_ALERTS_PER_EMAIL',
+ default=7,
+ description=_('Maximum number of news entries in an email alert')
+ )
+)
+
+settings.register(
+ BooleanValue(
+ EMAIL,
+ 'EMAIL_VALIDATION',
+ default=False,
+ hidden=True,
+ description=_('Require email verification before allowing to post'),
+ help_text=_('Active email verification is done by sending a verification key in email')
+ )
+)
+
+settings.register(
+ BooleanValue(
+ EMAIL,
+ 'EMAIL_UNIQUE',
+ default=True,
+ description=_('Allow only one account per email address')
+ )
+)
+
+settings.register(
+ StringValue(
+ EMAIL,
+ 'ANONYMOUS_USER_EMAIL',
+ default='anonymous@askbot.org',
+ description=_('Fake email for anonymous user'),
+ help_text=_('Use this setting to control gravatar for email-less user')
+ )
+)
diff --git a/forum/conf/external_keys.py b/forum/conf/external_keys.py
new file mode 100644
index 00000000..f43e1120
--- /dev/null
+++ b/forum/conf/external_keys.py
@@ -0,0 +1,93 @@
+"""
+External service key settings
+"""
+from forum.conf.settings_wrapper import settings
+from livesettings import ConfigurationGroup, StringValue
+from django.utils.translation import ugettext as _
+from django.conf import settings as django_settings
+
+EXTERNAL_KEYS = ConfigurationGroup(
+ 'EXTERNAL_KEYS',
+ _('Keys to connect the site with external services like Facebook, etc.')
+ )
+
+settings.register(
+ StringValue(
+ EXTERNAL_KEYS,
+ 'GOOGLE_SITEMAP_CODE',
+ description=_('Google site verification key'),
+ help_text=_(
+ 'This key helps google index your site '
+ 'please obtain is at '
+ '<a href="%(google_webmasters_tools_url)s">'
+ 'google webmasters tools site</a>'
+ ) % {'google_webmasters_tools_url':
+ 'https://www.google.com/webmasters/tools/home?hl=' \
+ + django_settings.LANGUAGE_CODE}
+ )
+)
+
+settings.register(
+ StringValue(
+ EXTERNAL_KEYS,
+ 'GOOGLE_ANALYTICS_KEY',
+ description=_('Google Analytics key'),
+ help_text=_(
+ 'Obtain is at <a href="%(ga_site)s">'
+ 'Google Analytics</a> site, if you '
+ 'wish to use Google Analytics to monitor '
+ 'your site'
+ ) % {'ga_site':'http://www.google.com/intl/%s/analytics/' \
+ % django_settings.LANGUAGE_CODE }
+ )
+)
+
+settings.register(
+ StringValue(
+ EXTERNAL_KEYS,
+ 'RECAPTCHA_PRIVATE_KEY',
+ description=_('Recaptcha private key') + ' - does not work yet',
+ hidden=True,
+ help_text=_(
+ 'Recaptcha is a tool that helps distinguish '
+ 'real people from annoying spam robots. '
+ 'Please get this and a public key at '
+ 'the <a href="http://recaptcha.net">recaptcha.net</a>'
+ )
+ )
+)
+
+settings.register(
+ StringValue(
+ EXTERNAL_KEYS,
+ 'RECAPTCHA_PUBLIC_KEY',
+ hidden=True,
+ description=_('Recaptcha public key') + ' - does not work yet'
+ )
+)
+
+settings.register(
+ StringValue(
+ EXTERNAL_KEYS,
+ 'FB_API_KEY',
+ description=_('Facebook public API key') + ' - does not work yet',
+ hidden=True,
+ help_text=_(
+ 'Facebook API key and Facebook secret '
+ 'allow to use Facebook Connect login method '
+ 'at your site. Please obtain these keys '
+ 'at <a href="http://www.facebook.com/developers/createapp.php">'
+ 'facebook create app</a> site'
+ )
+ )
+
+)
+
+settings.register(
+ StringValue(
+ EXTERNAL_KEYS,
+ 'FB_SECRET',
+ hidden=True,
+ description=_('Facebook secret key') + ' - does not work yet'
+ )
+)
diff --git a/forum/conf/flatpages.py b/forum/conf/flatpages.py
new file mode 100644
index 00000000..eb6b646a
--- /dev/null
+++ b/forum/conf/flatpages.py
@@ -0,0 +1,37 @@
+"""
+Q&A forum flatpages (about, etc.)
+"""
+from forum.conf.settings_wrapper import settings
+from livesettings import ConfigurationGroup, LongStringValue
+from django.utils.translation import ugettext as _
+
+FLATPAGES = ConfigurationGroup(
+ 'FLATPAGES',
+ _('Flatpages - about, privacy policy, etc.')
+ )
+
+settings.register(
+ LongStringValue(
+ FLATPAGES,
+ 'FORUM_ABOUT',
+ description=_('Text the Q&A forum About page (html format)'),
+ help_text=\
+ _(
+ 'Save, then <a href="http://validator.w3.org/">'
+ 'use HTML validator</a> on the "about" page to check your input.'
+ )
+ )
+)
+
+settings.register(
+ LongStringValue(
+ FLATPAGES,
+ 'FORUM_PRIVACY',
+ description=_('Text the Q&A forum Privacy Policy (html format)'),
+ help_text=\
+ _(
+ 'Save, then <a href="http://validator.w3.org/">'
+ 'use HTML validator</a> on the "privacy" page to check your input.'
+ )
+ )
+)
diff --git a/forum/conf/forum_data_rules.py b/forum/conf/forum_data_rules.py
new file mode 100644
index 00000000..e452ea7b
--- /dev/null
+++ b/forum/conf/forum_data_rules.py
@@ -0,0 +1,62 @@
+"""
+Settings for forum data display and entry
+"""
+from forum.conf.settings_wrapper import settings
+from livesettings import ConfigurationGroup, BooleanValue, IntegerValue
+from livesettings import StringValue
+from django.utils.translation import ugettext as _
+from forum import const
+
+FORUM_DATA_RULES = ConfigurationGroup(
+ 'FORUM_DATA_RULES',
+ _('Settings for forum data entry and display')
+ )
+
+settings.register(
+ BooleanValue(
+ FORUM_DATA_RULES,
+ 'WIKI_ON',
+ default=True,
+ description=_('Check to enable community wiki feature')
+ )
+)
+
+settings.register(
+ IntegerValue(
+ FORUM_DATA_RULES,
+ 'MAX_TAG_LENGTH',
+ default=20,
+ description=_('Maximum length of tag (number of characters)')
+ )
+)
+
+settings.register(
+ IntegerValue(
+ FORUM_DATA_RULES,
+ 'MAX_TAGS_PER_POST',
+ default=5,
+ description=_('Maximum number of tags per question')
+ )
+)
+
+#todo: looks like there is a bug in livesettings
+#that does not allow Integer values with defaults and choices
+settings.register(
+ StringValue(
+ FORUM_DATA_RULES,
+ 'DEFAULT_QUESTIONS_PAGE_SIZE',
+ choices=const.PAGE_SIZE_CHOICES,
+ default='30',
+ description=_('Number of questions to list by default')
+ )
+)
+
+settings.register(
+ StringValue(
+ FORUM_DATA_RULES,
+ 'UNANSWERED_QUESTION_MEANING',
+ choices=const.UNANSWERED_QUESTION_MEANING_CHOICES,
+ default='NO_ACCEPTED_ANSWERS',
+ description=_('What should "unanswered question" mean?')
+ )
+)
diff --git a/forum/conf/minimum_reputation.py b/forum/conf/minimum_reputation.py
new file mode 100644
index 00000000..a83d94fd
--- /dev/null
+++ b/forum/conf/minimum_reputation.py
@@ -0,0 +1,148 @@
+"""
+Settings for minimum reputation required for
+a variety of actions on the askbot forum
+"""
+from forum.conf.settings_wrapper import settings
+from livesettings import ConfigurationGroup, IntegerValue
+from django.utils.translation import ugettext as _
+
+MIN_REP = ConfigurationGroup(
+ 'MIN_REP',
+ _('Minimum reputation required to perform actions'),
+ ordering=0
+ )
+
+settings.register(
+ IntegerValue(
+ MIN_REP,
+ 'MIN_REP_TO_VOTE_UP',
+ default=15,
+ description=_('Upvote')
+ )
+ )
+
+settings.register(
+ IntegerValue(
+ MIN_REP,
+ 'MIN_REP_TO_VOTE_DOWN',
+ default=100,
+ description=_('Downvote')
+ )
+ )
+
+settings.register(
+ IntegerValue(
+ MIN_REP,
+ 'MIN_REP_TO_FLAG_OFFENSIVE',
+ default=15,
+ description=_('Flag offensive')
+ )
+ )
+
+settings.register(
+ IntegerValue(
+ MIN_REP,
+ 'MIN_REP_TO_LEAVE_COMMENTS',
+ default=50,
+ description=_('Leave comments')
+ )
+ )
+
+settings.register(
+ IntegerValue(
+ MIN_REP,
+ 'MIN_REP_TO_DELETE_OTHERS_COMMENTS',
+ default=2000,
+ description=_('Delete comments posted by others')
+ )
+ )
+
+settings.register(
+ IntegerValue(
+ MIN_REP,
+ 'MIN_REP_TO_UPLOAD_FILES',
+ default=60,
+ description=_('Upload files')
+ )
+ )
+
+settings.register(
+ IntegerValue(
+ MIN_REP,
+ 'MIN_REP_TO_CLOSE_OWN_QUESTIONS',
+ default=250,
+ description=_('Close own questions'),
+ )
+ )
+
+settings.register(
+ IntegerValue(
+ MIN_REP,
+ 'MIN_REP_TO_RETAG_OTHERS_QUESTIONS',
+ default=500,
+ description=_('Retag questions posted by other people')
+ )
+ )
+
+settings.register(
+ IntegerValue(
+ MIN_REP,
+ 'MIN_REP_TO_REOPEN_OWN_QUESTIONS',
+ default=500,
+ description=_('Reopen own questions')
+ )
+ )
+
+settings.register(
+ IntegerValue(
+ MIN_REP,
+ 'MIN_REP_TO_EDIT_WIKI',
+ default=750,
+ description=_('Edit community wiki posts')
+ )
+ )
+
+settings.register(
+ IntegerValue(
+ MIN_REP,
+ 'MIN_REP_TO_EDIT_OTHERS_POSTS',
+ default=2000,
+ description=_('Edit posts authored by other people')
+ )
+ )
+
+settings.register(
+ IntegerValue(
+ MIN_REP,
+ 'MIN_REP_TO_VIEW_OFFENSIVE_FLAGS',
+ default=2000,
+ description=_('View offensive flags')
+ )
+ )
+
+settings.register(
+ IntegerValue(
+ MIN_REP,
+ 'MIN_REP_TO_DISABLE_URL_NOFOLLOW',
+ default=2000,
+ description=_('Disable nofollow directive on links')
+ )
+ )
+
+settings.register(
+ IntegerValue(
+ MIN_REP,
+ 'MIN_REP_TO_CLOSE_OTHERS_QUESTIONS',
+ default=2000,
+ description=_('Close questions asked by others')
+ )
+ )
+
+settings.register(
+ IntegerValue(
+ MIN_REP,
+ 'MIN_REP_TO_LOCK_POSTS',
+ default=4000,
+ description=_('Lock posts')
+ )
+ )
diff --git a/forum/conf/reputation_changes.py b/forum/conf/reputation_changes.py
new file mode 100644
index 00000000..cef177f5
--- /dev/null
+++ b/forum/conf/reputation_changes.py
@@ -0,0 +1,149 @@
+"""
+Settings for reputation changes that apply to
+user in response to various actions by the same
+users or others
+"""
+from forum.conf.settings_wrapper import settings
+from livesettings import ConfigurationGroup, IntegerValue
+from django.utils.translation import ugettext as _
+
+REP_CHANGES = ConfigurationGroup(
+ 'REP_CHANGES',
+ _('Reputaion loss and gain rules'),
+ ordering=2
+ )
+
+settings.register(
+ IntegerValue(
+ REP_CHANGES,
+ 'MAX_REP_GAIN_PER_USER_PER_DAY',
+ default=200,
+ description=_('Maximum daily reputation gain per user')
+ )
+)
+
+settings.register(
+ IntegerValue(
+ REP_CHANGES,
+ 'REP_GAIN_FOR_RECEIVING_UPVOTE',
+ default=10,
+ description=_('Gain for receiving an upvote')
+ )
+)
+
+settings.register(
+ IntegerValue(
+ REP_CHANGES,
+ 'REP_GAIN_FOR_RECEIVING_ANSWER_ACCEPTANCE',
+ default=15,
+ description=_('Gain for the author of accepted answer')
+ )
+)
+
+settings.register(
+ IntegerValue(
+ REP_CHANGES,
+ 'REP_GAIN_FOR_ACCEPTING_ANSWER',
+ default=2,
+ description=_('Gain for accepting best answer')
+ )
+)
+
+settings.register(
+ IntegerValue(
+ REP_CHANGES,
+ 'REP_GAIN_FOR_RECEIVING_DOWNVOTE_CANCELATION',
+ default=2,
+ description=_('Gain for post owner on canceled downvote')
+ )
+)
+
+settings.register(
+ IntegerValue(
+ REP_CHANGES,
+ 'REP_GAIN_FOR_CANCELING_DOWNVOTE',
+ default=1,
+ description=_('Gain for voter on canceling downvote')
+ )
+)
+#'gain_by_canceling_downvote',
+
+settings.register(
+ IntegerValue(
+ REP_CHANGES,
+ 'REP_LOSS_FOR_CANCELING_ANSWER_ACCEPTANCE',
+ default=-2,
+ description=_('Loss for voter for canceling of answer acceptance')
+ )
+)
+#'lose_by_canceling_accepted_answer',
+
+settings.register(
+ IntegerValue(
+ REP_CHANGES,
+ 'REP_LOSS_FOR_RECEIVING_CANCELATION_OF_ANSWER_ACCEPTANCE',
+ default=-5,
+ description=_('Loss for author whose answer was "un-accepted"')
+ )
+)
+#'lose_by_accepted_answer_cancled',
+
+settings.register(
+ IntegerValue(
+ REP_CHANGES,
+ 'REP_LOSS_FOR_DOWNVOTING',
+ default=-2,
+ description=_('Loss for giving a downvote')
+ )
+)
+#'lose_by_downvoted',
+
+settings.register(
+ IntegerValue(
+ REP_CHANGES,
+ 'REP_LOSS_FOR_RECEIVING_FLAG',
+ default=-2,
+ description=_('Loss for owner of post that was flagged offensive')
+ )
+)
+#'lose_by_flagged',
+
+settings.register(
+ IntegerValue(
+ REP_CHANGES,
+ 'REP_LOSS_FOR_RECEIVING_DOWNVOTE',
+ default=-1,
+ description=_('Loss for owner of post that was downvoted')
+ )
+)
+#'lose_by_downvoting',
+
+settings.register(
+ IntegerValue(
+ REP_CHANGES,
+ 'REP_LOSS_FOR_RECEIVING_THREE_FLAGS_PER_REVISION',
+ default=-30,
+ description=_('Loss for owner of post that was flagged 3 times per same revision')
+ )
+)
+#'lose_by_flagged_lastrevision_3_times',
+
+settings.register(
+ IntegerValue(
+ REP_CHANGES,
+ 'REP_LOSS_FOR_RECEIVING_FIVE_FLAGS_PER_REVISION',
+ default=-100,
+ description=_('Loss for owner of post that was flagged 5 times per same revision')
+ )
+)
+#'lose_by_flagged_lastrevision_5_times',
+
+settings.register(
+ IntegerValue(
+ REP_CHANGES,
+ 'REP_LOSS_FOR_RECEIVING_UPVOTE_CANCELATION',
+ default=-10,
+ description=_('Loss for post owner when upvote is canceled')
+ )
+)
+#'lose_by_upvote_canceled',
diff --git a/forum/conf/settings_wrapper.py b/forum/conf/settings_wrapper.py
new file mode 100644
index 00000000..86d41709
--- /dev/null
+++ b/forum/conf/settings_wrapper.py
@@ -0,0 +1,70 @@
+"""
+Definition of a Singleton wrapper class for livesettings
+with interface similar to django.conf.settings
+that is each setting has unique key and is accessible
+via dotted lookup.
+
+for example to lookup value of setting BLAH you would do
+
+from forum.conf import settings as forum_settings
+
+forum_settings.BLAH
+
+NOTE that at the moment there is distinction between settings
+(django settings) and forum_settings (livesettings)
+
+the value will be taken from livesettings database or cache
+note that during compilation phase database is not accessible
+for the most part, so actual values are reliably available only
+at run time
+
+livesettings is a module developed for satchmo project
+"""
+from livesettings import SortedDotDict, config_register
+
+class ConfigSettings(object):
+ """A very simple Singleton wrapper for settings
+ a limitation is that all settings names using this class
+ must be distinct, even though they might belong
+ to different settings groups
+ """
+ __instance = None
+
+ def __init__(self):
+ """assigns SortedDotDict to self.__instance if not set"""
+ if ConfigSettings.__instance == None:
+ ConfigSettings.__instance = SortedDotDict()
+ self.__dict__['_ConfigSettings__instance'] = ConfigSettings.__instance
+ self.__ordering_index = {}
+
+ def __getattr__(self, key):
+ """value lookup returns the actual value of setting
+ not the object - this way only very minimal modifications
+ will be required in code to convert an app
+ depending on django.conf.settings to livesettings
+ """
+ return getattr(self.__instance, key).value
+
+ def register(self, value):
+ """registers the setting
+ value must be a subclass of livesettings.Value
+ """
+ key = value.key
+ group_key = value.group.key
+
+ ordering = self.__ordering_index.get(group_key, None)
+ if ordering:
+ ordering += 1
+ value.ordering = ordering
+ else:
+ ordering = 1
+ value.ordering = ordering
+ self.__ordering_index[group_key] = ordering
+
+ if key in self.__instance:
+ raise Exception('setting %s is already registered' % key)
+ else:
+ self.__instance[key] = config_register(value)
+
+#settings instance to be used elsewhere in the project
+settings = ConfigSettings()
diff --git a/forum/conf/site_settings.py b/forum/conf/site_settings.py
new file mode 100644
index 00000000..19445bda
--- /dev/null
+++ b/forum/conf/site_settings.py
@@ -0,0 +1,95 @@
+"""
+Q&A website settings - title, desctiption, basic urls
+keywords
+"""
+from forum.conf.settings_wrapper import settings
+from livesettings import ConfigurationGroup, StringValue
+from django.utils.translation import ugettext as _
+from django.utils.html import escape
+from django.conf import settings as django_settings
+from forum import const
+
+QA_SITE_SETTINGS = ConfigurationGroup(
+ 'QA_SITE_SETTINGS',
+ _('Q&A forum website parameters and urls')
+ )
+
+settings.register(
+ StringValue(
+ QA_SITE_SETTINGS,
+ 'APP_TITLE',
+ default=u'ASKBOT: Open Source Q&A Forum',
+ description=_('Site title for the Q&A forum')
+ )
+)
+
+settings.register(
+ StringValue(
+ QA_SITE_SETTINGS,
+ 'APP_KEYWORDS',
+ default=u'ASKBOT,CNPROG,forum,community',
+ description=_('Comma separated list of Q&A site keywords')
+ )
+)
+
+settings.register(
+ StringValue(
+ QA_SITE_SETTINGS,
+ 'APP_COPYRIGHT',
+ default='Copyright ASKBOT, 2010. Some rights reserved under creative commons license.',
+ description=_('Copyright message to show in the footer')
+ )
+)
+
+settings.register(
+ StringValue(
+ QA_SITE_SETTINGS,
+ 'APP_DESCRIPTION',
+ default='Open source question and answer forum written in Python and Django',
+ description=_('Site description for the search engines')
+ )
+)
+
+settings.register(
+ StringValue(
+ QA_SITE_SETTINGS,
+ 'APP_SHORT_NAME',
+ default=_('Askbot'),
+ hidden=True,
+ description=_('Short name for your Q&A forum')
+ )
+)
+
+settings.register(
+ StringValue(
+ QA_SITE_SETTINGS,
+ 'APP_URL',
+ default='http://askbot.org',
+ description=_('Base URL for your Q&A forum, must start with http or https'),
+ )
+)
+
+settings.register(
+ StringValue(
+ QA_SITE_SETTINGS,
+ 'GREETING_URL',
+ default='/' + _('faq/'),#cannot reverse url here, unfortunately, must be absolute also
+ hidden=True,
+ description=_('Link shown in the greeting message shown to the anonymous user'),
+ help_text=_('If you change this url from the default - '
+ 'then you will also probably want to adjust translation of '
+ 'the following string: ') + '"'
+ + escape(const.GREETING_FOR_ANONYMOUS_USER + '"'
+ ' You can find this string in your locale django.po file'
+ )
+ )
+)
+
+settings.register(
+ StringValue(
+ QA_SITE_SETTINGS,
+ 'FEEDBACK_SITE_URL',
+ description=_('Feedback site URL'),
+ help_text=_('If left empty, a simple internal feedback form will be used instead')
+ )
+)
diff --git a/forum/conf/skin_counter_settings.py b/forum/conf/skin_counter_settings.py
new file mode 100644
index 00000000..51c7e332
--- /dev/null
+++ b/forum/conf/skin_counter_settings.py
@@ -0,0 +1,251 @@
+"""
+Skin settings to color view, vote and answer counters
+"""
+from forum.conf.settings_wrapper import settings
+from livesettings import ConfigurationGroup, IntegerValue, StringValue
+from django.utils.translation import ugettext as _
+from forum_modules.grapefruit import Color
+
+SKIN_COUNTER_SETTINGS = ConfigurationGroup(
+ 'SKIN_COUNTER_SETTINGS',
+ _('Skin: view, vote and answer counters')
+ )
+
+settings.register(
+ IntegerValue(
+ SKIN_COUNTER_SETTINGS,
+ 'VOTE_COUNTER_EXPECTED_MAXIMUM',
+ default=3,
+ description=_('Vote counter value to give "full color"')
+ )
+)
+
+settings.register(
+ StringValue(
+ SKIN_COUNTER_SETTINGS,
+ 'COLORS_VOTE_COUNTER_EMPTY_BG',
+ default='white',
+ description=_('Background color for votes = 0'),
+ help_text=_('HTML color name of hex value')
+ )
+)
+
+settings.register(
+ StringValue(
+ SKIN_COUNTER_SETTINGS,
+ 'COLORS_VOTE_COUNTER_EMPTY_FG',
+ default='gray',
+ description=_('Foreground color for votes = 0'),
+ help_text=_('HTML color name of hex value')
+ )
+)
+
+settings.register(
+ StringValue(
+ SKIN_COUNTER_SETTINGS,
+ 'COLORS_VOTE_COUNTER_MIN_BG',
+ default='white',
+ description=_('Background color for votes = 1'),
+ help_text=_('HTML color name of hex value')
+ )
+)
+
+settings.register(
+ StringValue(
+ SKIN_COUNTER_SETTINGS,
+ 'COLORS_VOTE_COUNTER_MIN_FG',
+ default='black',
+ description=_('Foreground color for votes = 1'),
+ help_text=_('HTML color name of hex value')
+ )
+)
+
+settings.register(
+ StringValue(
+ SKIN_COUNTER_SETTINGS,
+ 'COLORS_VOTE_COUNTER_MAX_BG',
+ default='#a9d0f5',
+ description=_('Background color for votes = MAX'),
+ help_text=_('HTML color name of hex value')
+ )
+)
+
+settings.register(
+ StringValue(
+ SKIN_COUNTER_SETTINGS,
+ 'COLORS_VOTE_COUNTER_MAX_FG',
+ default=Color.NewFromHtml(
+ settings.COLORS_VOTE_COUNTER_MAX_BG
+ ).DarkerColor(0.7).html,
+ description=_('Foreground color for votes = MAX'),
+ help_text=_('HTML color name of hex value')
+ )
+)
+
+settings.register(
+ IntegerValue(
+ SKIN_COUNTER_SETTINGS,
+ 'VIEW_COUNTER_EXPECTED_MAXIMUM',
+ default=100,
+ description=_('View counter value to give "full color"')
+ )
+)
+
+settings.register(
+ StringValue(
+ SKIN_COUNTER_SETTINGS,
+ 'COLORS_VIEW_COUNTER_EMPTY_BG',
+ default='gray',
+ description=_('Background color for views = 0'),
+ help_text=_('HTML color name of hex value')
+ )
+)
+
+settings.register(
+ StringValue(
+ SKIN_COUNTER_SETTINGS,
+ 'COLORS_VIEW_COUNTER_EMPTY_FG',
+ default='white',
+ description=_('Foreground color for views = 0'),
+ help_text=_('HTML color name of hex value')
+ )
+)
+
+settings.register(
+ StringValue(
+ SKIN_COUNTER_SETTINGS,
+ 'COLORS_VIEW_COUNTER_MIN_BG',
+ default='#D0F5A9',
+ description=_('Background color for views = 1'),
+ help_text=_('HTML color name of hex value')
+ )
+)
+
+settings.register(
+ StringValue(
+ SKIN_COUNTER_SETTINGS,
+ 'COLORS_VIEW_COUNTER_MIN_FG',
+ default=Color.NewFromHtml(
+ settings.COLORS_VIEW_COUNTER_MIN_BG
+ ).DarkerColor(0.6).html,
+ description=_('Foreground color for views = 1'),
+ help_text=_('HTML color name of hex value')
+ )
+)
+
+settings.register(
+ StringValue(
+ SKIN_COUNTER_SETTINGS,
+ 'COLORS_VIEW_COUNTER_MAX_BG',
+ default='#FF8000',
+ description=_('Background color for views = MAX'),
+ help_text=_('HTML color name of hex value')
+ )
+)
+
+settings.register(
+ StringValue(
+ SKIN_COUNTER_SETTINGS,
+ 'COLORS_VIEW_COUNTER_MAX_FG',
+ default=Color.NewFromHtml(
+ settings.COLORS_VIEW_COUNTER_MAX_BG
+ ).DarkerColor(0.7).html,
+ description=_('Foreground color for views = MAX'),
+ help_text=_('HTML color name of hex value')
+ )
+)
+
+settings.register(
+ IntegerValue(
+ SKIN_COUNTER_SETTINGS,
+ 'ANSWER_COUNTER_EXPECTED_MAXIMUM',
+ default=4,
+ description=_('Answer counter value to give "full color"')
+ )
+)
+
+settings.register(
+ StringValue(
+ SKIN_COUNTER_SETTINGS,
+ 'COLORS_ANSWER_COUNTER_EMPTY_BG',
+ default=Color.NewFromHtml('#a40000').Blend(
+ Color.NewFromHtml('white'),0.8
+ ).html,
+ description=_('Background color for answers = 0'),
+ help_text=_('HTML color name of hex value')
+ )
+)
+
+settings.register(
+ StringValue(
+ SKIN_COUNTER_SETTINGS,
+ 'COLORS_ANSWER_COUNTER_EMPTY_FG',
+ default='yellow',
+ description=_('Foreground color for answers = 0'),
+ help_text=_('HTML color name of hex value')
+ )
+)
+
+settings.register(
+ StringValue(
+ SKIN_COUNTER_SETTINGS,
+ 'COLORS_ANSWER_COUNTER_MIN_BG',
+ default='#AEB404',
+ description=_('Background color for answers = 1'),
+ help_text=_('HTML color name of hex value')
+ )
+)
+
+settings.register(
+ StringValue(
+ SKIN_COUNTER_SETTINGS,
+ 'COLORS_ANSWER_COUNTER_MIN_FG',
+ default='white',
+ description=_('Foreground color for answers = 1'),
+ help_text=_('HTML color name of hex value')
+ )
+)
+
+settings.register(
+ StringValue(
+ SKIN_COUNTER_SETTINGS,
+ 'COLORS_ANSWER_COUNTER_MAX_BG',
+ default=Color.NewFromHtml('#61380B').Blend(
+ Color.NewFromHtml('white'),0.75
+ ).html,
+ description=_('Background color for answers = MAX'),
+ help_text=_('HTML color name of hex value')
+ )
+)
+
+settings.register(
+ StringValue(
+ SKIN_COUNTER_SETTINGS,
+ 'COLORS_ANSWER_COUNTER_MAX_FG',
+ default='#ffff00',
+ description=_('Foreground color for answers = MAX'),
+ help_text=_('HTML color name of hex value')
+ )
+)
+
+settings.register(
+ StringValue(
+ SKIN_COUNTER_SETTINGS,
+ 'COLORS_ANSWER_COUNTER_ACCEPTED_BG',
+ default=Color.NewFromHtml('darkgreen').Blend(
+ Color.NewFromHtml('white'),0.8
+ ).html,
+ description=_('Background color for accepted'),
+ help_text=_('HTML color name of hex value')
+ )
+)
+
+settings.register(
+ StringValue(
+ SKIN_COUNTER_SETTINGS,
+ 'COLORS_ANSWER_COUNTER_ACCEPTED_FG',
+ default='#D0F5A9',
+ description=_('Foreground color for accepted answer'),
+ help_text=_('HTML color name of hex value')
+ )
+)
diff --git a/forum/conf/skin_general_settings.py b/forum/conf/skin_general_settings.py
new file mode 100644
index 00000000..166cd603
--- /dev/null
+++ b/forum/conf/skin_general_settings.py
@@ -0,0 +1,38 @@
+"""
+General skin settings
+"""
+from forum.conf.settings_wrapper import settings
+from livesettings import ConfigurationGroup, StringValue, IntegerValue
+from django.utils.translation import ugettext as _
+from forum.skins import get_skin_choices
+
+GENERAL_SKIN_SETTINGS = ConfigurationGroup(
+ 'GENERAL_SKIN_SETTINGS',
+ _('Skin: general settings'),
+ )
+
+settings.register(
+ StringValue(
+ GENERAL_SKIN_SETTINGS,
+ 'ASKBOT_DEFAULT_SKIN',
+ default='default',
+ choices=get_skin_choices(),
+ description=_('Select skin'),
+ )
+)
+
+settings.register(
+ IntegerValue(
+ GENERAL_SKIN_SETTINGS,
+ 'MEDIA_RESOURCE_REVISION',
+ default=1,
+ description=_('Skin media revision number'),
+ help_text=_(
+ 'Increment this number when you change '
+ 'image in skin media or stylesheet. '
+ 'This helps avoid showing your users '
+ 'outdated images from their browser cache.'
+ )
+ )
+)
+
diff --git a/forum/conf/user_settings.py b/forum/conf/user_settings.py
new file mode 100644
index 00000000..760f921f
--- /dev/null
+++ b/forum/conf/user_settings.py
@@ -0,0 +1,30 @@
+"""
+User policy settings
+"""
+from forum.conf.settings_wrapper import settings
+from livesettings import ConfigurationGroup, BooleanValue, IntegerValue
+from django.utils.translation import ugettext as _
+
+USER_SETTINGS = ConfigurationGroup(
+ 'USER_SETTINGS',
+ _('User policy settings')
+ )
+
+settings.register(
+ BooleanValue(
+ USER_SETTINGS,
+ 'EDITABLE_SCREEN_NAME',
+ default=True,
+ description=_('Allow editing user screen name')
+ )
+)
+
+settings.register(
+ IntegerValue(
+ USER_SETTINGS,
+ 'MIN_USERNAME_LENGTH',
+ hidden=True,
+ default=1,
+ description=_('Minimum allowed length for screen name')
+ )
+)
diff --git a/forum/conf/vote_rules.py b/forum/conf/vote_rules.py
new file mode 100644
index 00000000..f249ef53
--- /dev/null
+++ b/forum/conf/vote_rules.py
@@ -0,0 +1,69 @@
+"""
+Forum configuration settings detailing rules on votes
+and offensive flags.
+
+For example number of times a person can vote each day, etc.
+"""
+from forum.conf.settings_wrapper import settings
+from livesettings import ConfigurationGroup, IntegerValue
+from django.utils.translation import ugettext as _
+
+VOTE_RULES = ConfigurationGroup(
+ 'VOTE_RULES',
+ _('Limits applicable to votes and moderation flags'),
+ ordering=1,
+ )
+
+settings.register(
+ IntegerValue(
+ VOTE_RULES,
+ 'MAX_VOTES_PER_USER_PER_DAY',
+ default=30,
+ description=_('Number of votes a user can cast per day')
+ )
+)
+
+settings.register(
+ IntegerValue(
+ VOTE_RULES,
+ 'MAX_FLAGS_PER_USER_PER_DAY',
+ default=5,
+ description=_('Maximum number of flags per user per day')
+ )
+)
+
+settings.register(
+ IntegerValue(
+ VOTE_RULES,
+ 'VOTES_LEFT_WARNING_THRESHOLD',
+ default=5,
+ description=_('Threshold for warning about remaining daily votes')
+ )
+)
+
+settings.register(
+ IntegerValue(
+ VOTE_RULES,
+ 'MAX_DAYS_TO_CANCEL_VOTE',
+ default=1,
+ description=_('Number of days to allow canceling votes')
+ )
+)
+
+settings.register(
+ IntegerValue(
+ VOTE_RULES,
+ 'MIN_FLAGS_TO_HIDE_POST',
+ default=3,
+ description=_('Number of flags required to automatically hide posts')
+ )
+)
+
+settings.register(
+ IntegerValue(
+ VOTE_RULES,
+ 'MIN_FLAGS_TO_DELETE_POST',
+ default=5,
+ description=_('Number of flags required to automatically delete posts')
+ )
+)
diff --git a/forum/const.py b/forum/const/__init__.py
index 07b0291c..50676189 100755..100644
--- a/forum/const.py
+++ b/forum/const/__init__.py
@@ -54,12 +54,15 @@ POST_SCOPE_LIST = (
('favorite', _('favorite')),
)
DEFAULT_POST_SCOPE = 'all'
-DEFAULT_QUESTIONS_PAGE_SIZE = 30
PAGE_SIZE_CHOICES = (('10','10',),('30','30',),('50','50',),)
-UNANSWERED_MEANING_LIST = ('NO_ANSWERS','NO_UPVOTED_ANSWERS','NO_ACCEPTED_ANSWERS')
-UNANSWERED_MEANING = 'NO_ACCEPTED_ANSWERS'
-assert(UNANSWERED_MEANING in UNANSWERED_MEANING_LIST)
+UNANSWERED_QUESTION_MEANING_CHOICES = (
+ ('NO_ANSWERS', _('Question has no answers')),
+ ('NO_ACCEPTED_ANSWERS', _('Question has no accepted answers')),
+)
+#todo: implement this
+# ('NO_UPVOTED_ANSWERS',),
+#)
#todo:
#this probably needs to be language-specific
@@ -69,8 +72,6 @@ assert(UNANSWERED_MEANING in UNANSWERED_MEANING_LIST)
#to do full string match
TAG_REGEX = r'^[a-z0-9\+\.\-]+$'
TAG_SPLIT_REGEX = r'[ ,]+'
-MAX_TAG_LENGTH = 20 #default 20 chars
-MAX_TAGS_PER_POST = 5 #no more than five tags
TYPE_ACTIVITY_ASK_QUESTION=1
TYPE_ACTIVITY_ANSWER=2
@@ -130,5 +131,7 @@ CONST = {
#how to filter questions by tags in email digests?
TAG_EMAIL_FILTER_CHOICES = (('ignored', _('exclude ignored tags')),('interesting',_('allow only selected tags')))
-MAX_ALERTS_PER_EMAIL = 7
-USERS_PAGE_SIZE = 28
+USERS_PAGE_SIZE = 28#todo: move it to settings?
+
+#an exception import * because that file has only strings
+from forum.const.message_keys import *
diff --git a/forum/const/message_keys.py b/forum/const/message_keys.py
new file mode 100644
index 00000000..c52b9353
--- /dev/null
+++ b/forum/const/message_keys.py
@@ -0,0 +1,19 @@
+"""
+This file must hold keys for translatable messages
+that are used as variables
+it is important that a dummy _() function is used here
+this way message key will be pulled into django.po
+and can still be used as a variable in python files
+"""
+_ = lambda v:v
+
+#NOTE: all strings must be explicitly put into this dictionary,
+#because you don't want to import _ from here with import *
+__all__ = ['GREETING_FOR_ANONYMOUS_USER',]
+
+#this variable is shown in settings, because
+#the url within is configurable, the default is reverse('faq')
+#if user changes url they will have to be able to fix the
+#message translation too
+GREETING_FOR_ANONYMOUS_USER = \
+ _('First time here? Check out the <a href="%s">FAQ</a>!')
diff --git a/forum/context.py b/forum/context.py
index 043af81d..47fb43f2 100644
--- a/forum/context.py
+++ b/forum/context.py
@@ -1,22 +1,24 @@
from django.conf import settings
+from forum.conf import settings as forum_settings
def application_settings(context):
my_settings = {
- 'APP_TITLE' : settings.APP_TITLE,
- 'APP_SHORT_NAME' : settings.APP_SHORT_NAME,
- 'APP_URL' : settings.APP_URL,
- 'APP_KEYWORDS' : settings.APP_KEYWORDS,
- 'APP_DESCRIPTION' : settings.APP_DESCRIPTION,
- 'APP_INTRO' : settings.APP_INTRO,
- 'EMAIL_VALIDATION': settings.EMAIL_VALIDATION,
- 'FEEDBACK_SITE_URL': settings.FEEDBACK_SITE_URL,
+ 'WIKI_ON':forum_settings.WIKI_ON,
+ 'APP_TITLE' : forum_settings.APP_TITLE,
+ 'APP_URL' : forum_settings.APP_URL,
+ 'APP_KEYWORDS' : forum_settings.APP_KEYWORDS,
+ 'APP_DESCRIPTION': forum_settings.APP_DESCRIPTION,
+ 'APP_COPYRIGHT': forum_settings.APP_COPYRIGHT,
+ 'FEEDBACK_SITE_URL': forum_settings.FEEDBACK_SITE_URL,
+ 'FORUM_ABOUT': forum_settings.FORUM_ABOUT,
+ 'FORUM_PRIVACY': forum_settings.FORUM_PRIVACY,
+ 'GOOGLE_SITEMAP_CODE':forum_settings.GOOGLE_SITEMAP_CODE,
+ 'GOOGLE_ANALYTICS_KEY':forum_settings.GOOGLE_ANALYTICS_KEY,
+ 'EMAIL_VALIDATION': forum_settings.EMAIL_VALIDATION,
+ 'RESOURCE_REVISION':forum_settings.MEDIA_RESOURCE_REVISION,
+ 'ASKBOT_SKIN':forum_settings.ASKBOT_DEFAULT_SKIN,
+ 'EDITABLE_SCREEN_NAME':forum_settings.EDITABLE_SCREEN_NAME,
'FORUM_SCRIPT_ALIAS': settings.FORUM_SCRIPT_ALIAS,
'LANGUAGE_CODE': settings.LANGUAGE_CODE,
- 'GOOGLE_SITEMAP_CODE':settings.GOOGLE_SITEMAP_CODE,
- 'GOOGLE_ANALYTICS_KEY':settings.GOOGLE_ANALYTICS_KEY,
- 'WIKI_ON':settings.WIKI_ON,
- 'RESOURCE_REVISION':settings.RESOURCE_REVISION,
- 'ASKBOT_SKIN':settings.ASKBOT_DEFAULT_SKIN,
- 'EDITABLE_SCREEN_NAME':settings.EDITABLE_SCREEN_NAME,
}
return {'settings':my_settings}
diff --git a/forum/documentation/HOW_TO_DEBUG b/forum/doc/HOW_TO_DEBUG
index fbbdb1f7..fbbdb1f7 100644
--- a/forum/documentation/HOW_TO_DEBUG
+++ b/forum/doc/HOW_TO_DEBUG
diff --git a/forum/documentation/INSTALL b/forum/doc/INSTALL
index 73714566..73714566 100644
--- a/forum/documentation/INSTALL
+++ b/forum/doc/INSTALL
diff --git a/forum/documentation/INSTALL.pip b/forum/doc/INSTALL.pip
index 2f817ff8..2f817ff8 100644
--- a/forum/documentation/INSTALL.pip
+++ b/forum/doc/INSTALL.pip
diff --git a/forum/documentation/INSTALL.webfaction b/forum/doc/INSTALL.webfaction
index a449ffe6..a449ffe6 100644
--- a/forum/documentation/INSTALL.webfaction
+++ b/forum/doc/INSTALL.webfaction
diff --git a/forum/documentation/ROADMAP.rst b/forum/doc/ROADMAP.rst
index c79e0ae4..c79e0ae4 100644
--- a/forum/documentation/ROADMAP.rst
+++ b/forum/doc/ROADMAP.rst
diff --git a/forum/documentation/TODO.rst b/forum/doc/TODO.rst
index b89013b0..b89013b0 100644
--- a/forum/documentation/TODO.rst
+++ b/forum/doc/TODO.rst
diff --git a/forum/documentation/UPGRADE b/forum/doc/UPGRADE
index 538b75a0..538b75a0 100644
--- a/forum/documentation/UPGRADE
+++ b/forum/doc/UPGRADE
diff --git a/forum/documentation/WISH_LIST b/forum/doc/WISH_LIST
index 2b53662c..2b53662c 100644
--- a/forum/documentation/WISH_LIST
+++ b/forum/doc/WISH_LIST
diff --git a/forum/documentation/askbot-requirements.txt b/forum/doc/askbot-requirements.txt
index 66a37fbe..66a37fbe 100644
--- a/forum/documentation/askbot-requirements.txt
+++ b/forum/doc/askbot-requirements.txt
diff --git a/forum/documentation/scratch b/forum/doc/scratch
index ca4e67e9..948055fa 100644
--- a/forum/documentation/scratch
+++ b/forum/doc/scratch
@@ -5,3 +5,8 @@ Name duplicates previous WSGI daemon definition
different keys - empty space counts for translation keys
{% blocktrans %}page number {{num}} {% endblocktrans %}
{% blocktrans %}page number {{num}}{% endblocktrans %}
+
+for admin interface downloaded two packages:
+django-keyedcache
+django-livesettings
+from http://bitbucket.org/bkroeze/
diff --git a/forum/feed.py b/forum/feed.py
index e4b929e9..94065120 100755..100644
--- a/forum/feed.py
+++ b/forum/feed.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#encoding:utf-8
#-------------------------------------------------------------------------------
-# Name: Syndication feed class for subsribtion
+# Name: Syndication feed class for subscription
# Purpose:
#
# Author: Mike
@@ -13,13 +13,13 @@
from django.contrib.syndication.feeds import Feed, FeedDoesNotExist
from django.utils.translation import ugettext as _
from models import Question
-from django.conf import settings
+from forum.conf import settings as forum_settings
class RssLastestQuestionsFeed(Feed):
- title = settings.APP_TITLE + _(' - ')+ _('latest questions')
- link = settings.APP_URL #+ '/' + _('question/')
- description = settings.APP_DESCRIPTION
+ title = forum_settings.APP_TITLE + _(' - ')+ _('latest questions')
+ link = forum_settings.APP_URL
+ description = forum_settings.APP_DESCRIPTION
#ttl = 10
- copyright = settings.APP_COPYRIGHT
+ copyright = forum_settings.APP_COPYRIGHT
def item_link(self, item):
return self.link + item.get_absolute_url()
diff --git a/forum/forms.py b/forum/forms.py
index e9781dc9..7e5ae755 100755..100644
--- a/forum/forms.py
+++ b/forum/forms.py
@@ -11,6 +11,7 @@ from django.contrib.contenttypes.models import ContentType
from forum.utils.forms import NextUrlField, UserNameField, SetPasswordForm
from recaptcha_django import ReCaptchaField
from django.conf import settings
+from forum.conf import settings as forum_settings
import logging
@@ -65,15 +66,16 @@ class TagNamesField(forms.CharField):
tag_strings = split_re.split(data)
out_tag_list = []
tag_count = len(tag_strings)
- if tag_count > const.MAX_TAGS_PER_POST:
+ if tag_count > forum_settings.MAX_TAGS_PER_POST:
+ max_tags = forum_settings.MAX_TAGS_PER_POST
msg = ungettext(
- 'please use %(tag_count)d tag or less',#odd but have to use to pluralize
+ 'please use %(tag_count)d tag or less',
'please use %(tag_count)d tags or less',
- tag_count) % {'tag_count':tag_count}
+ tag_count) % {'tag_count':max_tags}
raise forms.ValidationError(msg)
for tag in tag_strings:
tag_length = len(tag)
- if tag_length > const.MAX_TAG_LENGTH:
+ if tag_length > forum_settings.MAX_TAG_LENGTH:
#singular form is odd in english, but required for pluralization
#in other languages
msg = ungettext('each tag must be shorter than %(max_chars)d character',#odd but added for completeness
@@ -97,7 +99,7 @@ class WikiField(forms.BooleanField):
self.label = _('community wiki')
self.help_text = _('if you choose community wiki option, the question and answer do not generate points and name of author will not be shown')
def clean(self,value):
- return value and settings.WIKI_ON
+ return value and forum_settings.WIKI_ON
class EmailNotifyField(forms.BooleanField):
def __init__(self, *args, **kwargs):
@@ -231,7 +233,7 @@ class AnswerForm(forms.Form):
def __init__(self, question, user, *args, **kwargs):
super(AnswerForm, self).__init__(*args, **kwargs)
self.fields['email_notify'].widget.attrs['id'] = 'question-subscribe-updates';
- if question.wiki and settings.WIKI_ON:
+ if question.wiki and forum_settings.WIKI_ON:
self.fields['wiki'].initial = True
if user.is_authenticated():
if user in question.followed_by.all():
@@ -292,7 +294,7 @@ class EditAnswerForm(forms.Form):
class EditUserForm(forms.Form):
email = forms.EmailField(label=u'Email', help_text=_('this email does not have to be linked to gravatar'), required=True, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- if settings.EDITABLE_SCREEN_NAME:
+ if forum_settings.EDITABLE_SCREEN_NAME:
username = UserNameField(label=_('Screen name'))
realname = forms.CharField(label=_('Real name'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
website = forms.URLField(label=_('Website'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
@@ -303,7 +305,7 @@ class EditUserForm(forms.Form):
def __init__(self, user, *args, **kwargs):
super(EditUserForm, self).__init__(*args, **kwargs)
logging.debug('initializing the form')
- if settings.EDITABLE_SCREEN_NAME:
+ if forum_settings.EDITABLE_SCREEN_NAME:
self.fields['username'].initial = user.username
self.fields['username'].user_instance = user
self.fields['email'].initial = user.email
@@ -322,7 +324,7 @@ class EditUserForm(forms.Form):
"""For security reason one unique email in database"""
if self.user.email != self.cleaned_data['email']:
#todo dry it, there is a similar thing in openidauth
- if settings.EMAIL_UNIQUE == True:
+ if forum_settings.EMAIL_UNIQUE == True:
if 'email' in self.cleaned_data:
try:
user = User.objects.get(email = self.cleaned_data['email'])
diff --git a/forum/importers/stackexchange/management/commands/load_stackexchange.py b/forum/importers/stackexchange/management/commands/load_stackexchange.py
index ee22e33a..308ac0bb 100644
--- a/forum/importers/stackexchange/management/commands/load_stackexchange.py
+++ b/forum/importers/stackexchange/management/commands/load_stackexchange.py
@@ -11,7 +11,7 @@ import forum.models as askbot
import forum.importers.stackexchange.models as se
from forum.forms import EditUserEmailFeedsForm
from forum.utils.html import sanitize_html
-from django.conf import settings
+from forum.conf import settings as forum_settings
from django.contrib.auth.models import Message as DjangoMessage
from django.utils.translation import ugettext as _
#from markdown2 import Markdown
@@ -209,7 +209,7 @@ class X(object):#
@classmethod
def get_email(cls, email):#todo: fix fringe case - user did not give email!
if email is None:
- return settings.ANONYMOUS_USER_EMAIL
+ return forum_settings.ANONYMOUS_USER_EMAIL
else:
assert(email != '')
return email
diff --git a/forum/management/__init__.py b/forum/management/__init__.py
index b654caaa..b654caaa 100755..100644
--- a/forum/management/__init__.py
+++ b/forum/management/__init__.py
diff --git a/forum/management/commands/__init__.py b/forum/management/commands/__init__.py
index e69de29b..e69de29b 100755..100644
--- a/forum/management/commands/__init__.py
+++ b/forum/management/commands/__init__.py
diff --git a/forum/management/commands/base_command.py b/forum/management/commands/base_command.py
index c073bf7a..c073bf7a 100755..100644
--- a/forum/management/commands/base_command.py
+++ b/forum/management/commands/base_command.py
diff --git a/forum/management/commands/clean_award_badges.py b/forum/management/commands/clean_award_badges.py
index 117e3a5f..117e3a5f 100755..100644
--- a/forum/management/commands/clean_award_badges.py
+++ b/forum/management/commands/clean_award_badges.py
diff --git a/forum/management/commands/message_to_everyone.py b/forum/management/commands/message_to_everyone.py
index c020c178..c020c178 100755..100644
--- a/forum/management/commands/message_to_everyone.py
+++ b/forum/management/commands/message_to_everyone.py
diff --git a/forum/management/commands/multi_award_badges.py b/forum/management/commands/multi_award_badges.py
index 6b330cf9..6b330cf9 100755..100644
--- a/forum/management/commands/multi_award_badges.py
+++ b/forum/management/commands/multi_award_badges.py
diff --git a/forum/management/commands/once_award_badges.py b/forum/management/commands/once_award_badges.py
index 60457373..60457373 100755..100644
--- a/forum/management/commands/once_award_badges.py
+++ b/forum/management/commands/once_award_badges.py
diff --git a/forum/management/commands/pg_base_command.py b/forum/management/commands/pg_base_command.py
index b3167dcf..b3167dcf 100755..100644
--- a/forum/management/commands/pg_base_command.py
+++ b/forum/management/commands/pg_base_command.py
diff --git a/forum/management/commands/pg_clean_award_badges.py b/forum/management/commands/pg_clean_award_badges.py
index b3925a68..b3925a68 100755..100644
--- a/forum/management/commands/pg_clean_award_badges.py
+++ b/forum/management/commands/pg_clean_award_badges.py
diff --git a/forum/management/commands/pg_multi_award_badges.py b/forum/management/commands/pg_multi_award_badges.py
index 75f84bfe..75f84bfe 100755..100644
--- a/forum/management/commands/pg_multi_award_badges.py
+++ b/forum/management/commands/pg_multi_award_badges.py
diff --git a/forum/management/commands/pg_once_award_badges.py b/forum/management/commands/pg_once_award_badges.py
index b2f79363..b2f79363 100755..100644
--- a/forum/management/commands/pg_once_award_badges.py
+++ b/forum/management/commands/pg_once_award_badges.py
diff --git a/forum/management/commands/sample_command.py b/forum/management/commands/sample_command.py
index 55e67235..55e67235 100755..100644
--- a/forum/management/commands/sample_command.py
+++ b/forum/management/commands/sample_command.py
diff --git a/forum/management/commands/send_email_alerts.py b/forum/management/commands/send_email_alerts.py
index 5204a81e..db16f00f 100755..100644
--- a/forum/management/commands/send_email_alerts.py
+++ b/forum/management/commands/send_email_alerts.py
@@ -1,13 +1,14 @@
from django.core.management.base import NoArgsCommand
from django.db import connection
from django.db.models import Q, F
-from forum.models import *
-from forum import const
+from forum.models import User, Question, Answer, Tag, QuestionRevision
+from forum.models import AnswerRevision, Activity, EmailFeedSetting
from django.core.mail import EmailMessage
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
import datetime
from django.conf import settings
+from forum.conf import settings as forum_settings
import logging
from forum.utils.odict import OrderedDict
from django.contrib.contenttypes.models import ContentType
@@ -18,7 +19,7 @@ def extend_question_list(src, dst, limit=False):
or None
dst - is an ordered dictionary
"""
- if limit and len(dst.keys()) >= const.MAX_ALERTS_PER_EMAIL:
+ if limit and len(dst.keys()) >= forum_settings.MAX_ALERTS_PER_EMAIL:
return
if src is None:#is not QuerySet
return #will not do anything if subscription of this type is not used
@@ -110,16 +111,16 @@ class Command(NoArgsCommand):
q_ask_B = Q_set_B.filter(author=user)
q_ask_B.cutoff_time = cutoff_time
elif feed.feed_type == 'q_ans':
- q_ans_A = Q_set_A.filter(answers__author=user)[:const.MAX_ALERTS_PER_EMAIL]
+ q_ans_A = Q_set_A.filter(answers__author=user)[:forum_settings.MAX_ALERTS_PER_EMAIL]
q_ans_A.cutoff_time = cutoff_time
- q_ans_B = Q_set_B.filter(answers__author=user)[:const.MAX_ALERTS_PER_EMAIL]
+ q_ans_B = Q_set_B.filter(answers__author=user)[:forum_settings.MAX_ALERTS_PER_EMAIL]
q_ans_B.cutoff_time = cutoff_time
elif feed.feed_type == 'q_all':
if user.tag_filter_setting == 'ignored':
ignored_tags = Tag.objects.filter(user_selections__reason='bad', \
user_selections__user=user)
- q_all_A = Q_set_A.exclude( tags__in=ignored_tags )[:const.MAX_ALERTS_PER_EMAIL]
- q_all_B = Q_set_B.exclude( tags__in=ignored_tags )[:const.MAX_ALERTS_PER_EMAIL]
+ q_all_A = Q_set_A.exclude( tags__in=ignored_tags )[:forum_settings.MAX_ALERTS_PER_EMAIL]
+ q_all_B = Q_set_B.exclude( tags__in=ignored_tags )[:forum_settings.MAX_ALERTS_PER_EMAIL]
else:
selected_tags = Tag.objects.filter(user_selections__reason='good', \
user_selections__user=user)
@@ -232,7 +233,7 @@ class Command(NoArgsCommand):
else:
num_q += 1
if num_q > 0:
- url_prefix = settings.APP_URL
+ url_prefix = forum_settings.APP_URL
subject = _('email update message subject')
print 'have %d updated questions for %s' % (num_q, user.username)
text = ungettext('%(name)s, this is an update message header for %(num)d question',
@@ -246,7 +247,7 @@ class Command(NoArgsCommand):
act_list = []
if meta_data['skip']:
continue
- if items_added >= const.MAX_ALERTS_PER_EMAIL:
+ if items_added >= forum_settings.MAX_ALERTS_PER_EMAIL:
items_unreported = num_q - items_added #may be inaccurate actually, but it's ok
else:
@@ -261,7 +262,7 @@ class Command(NoArgsCommand):
% (url_prefix + q.get_absolute_url(), q.title, act_token)
text += '</ul>'
text += '<p></p>'
- #if len(q_list.keys()) >= const.MAX_ALERTS_PER_EMAIL:
+ #if len(q_list.keys()) >= forum_settings.MAX_ALERTS_PER_EMAIL:
# text += _('There may be more questions updated since '
# 'you have logged in last time as this list is '
# 'abridged for your convinience. Please visit '
diff --git a/forum/management/commands/subscribe_everyone.py b/forum/management/commands/subscribe_everyone.py
index c79528f3..c79528f3 100755..100644
--- a/forum/management/commands/subscribe_everyone.py
+++ b/forum/management/commands/subscribe_everyone.py
diff --git a/forum/middleware/__init__.py b/forum/middleware/__init__.py
index e69de29b..e69de29b 100755..100644
--- a/forum/middleware/__init__.py
+++ b/forum/middleware/__init__.py
diff --git a/forum/middleware/anon_user.py b/forum/middleware/anon_user.py
index 866734da..51d35fa7 100755..100644
--- a/forum/middleware/anon_user.py
+++ b/forum/middleware/anon_user.py
@@ -2,8 +2,9 @@ from django.http import HttpResponseRedirect
from forum.utils.forms import get_next_url
from django.utils.translation import ugettext as _
from forum.user_messages import create_message, get_and_delete_messages
-from django.conf import settings
from django.core.urlresolvers import reverse
+from forum.conf import settings as forum_settings
+from forum import const
import logging
class AnonymousMessageManager(object):
@@ -31,5 +32,5 @@ class ConnectToSessionMessagesMiddleware(object):
#also set the first greeting one time per session only
if 'greeting_set' not in request.session:
request.session['greeting_set'] = True
- msg = _('First time here? Check out the <a href="%s">FAQ</a>!') % reverse('faq')
+ msg = _(const.GREETING_FOR_ANONYMOUS_USER) % forum_settings.GREETING_URL
request.user.message_set.create(message=msg)
diff --git a/forum/middleware/cancel.py b/forum/middleware/cancel.py
index 15a4371d..15a4371d 100755..100644
--- a/forum/middleware/cancel.py
+++ b/forum/middleware/cancel.py
diff --git a/forum/middleware/pagesize.py b/forum/middleware/pagesize.py
index 486193dc..486193dc 100755..100644
--- a/forum/middleware/pagesize.py
+++ b/forum/middleware/pagesize.py
diff --git a/forum/models/__init__.py b/forum/models/__init__.py
index d0d2d4a7..024379db 100755..100644
--- a/forum/models/__init__.py
+++ b/forum/models/__init__.py
@@ -57,6 +57,8 @@ def user_get_absolute_url(self):
User.add_to_class('is_approved', models.BooleanField(default=False))
User.add_to_class('email_isvalid', models.BooleanField(default=False))
User.add_to_class('email_key', models.CharField(max_length=32, null=True))
+
+#hardcoded initial reputaion of 1, no setting for this one
User.add_to_class('reputation', models.PositiveIntegerField(default=1))
User.add_to_class('gravatar', models.CharField(max_length=32))
@@ -406,8 +408,8 @@ def record_user_full_updated(instance, **kwargs):
def post_stored_anonymous_content(sender,user,session_key,signal,*args,**kwargs):
aq_list = AnonymousQuestion.objects.filter(session_key = session_key)
aa_list = AnonymousAnswer.objects.filter(session_key = session_key)
- import settings
- if settings.EMAIL_VALIDATION == 'on':#add user to the record
+ from forum.conf import settings as forum_settings
+ if forum_settings.EMAIL_VALIDATION == True:#add user to the record
for aq in aq_list:
aq.author = user
aq.save()
diff --git a/forum/models/answer.py b/forum/models/answer.py
index 3de6cfc4..3de6cfc4 100755..100644
--- a/forum/models/answer.py
+++ b/forum/models/answer.py
diff --git a/forum/models/base.py b/forum/models/base.py
index fcec47b4..fcec47b4 100755..100644
--- a/forum/models/base.py
+++ b/forum/models/base.py
diff --git a/forum/models/meta.py b/forum/models/meta.py
index 114d2130..114d2130 100755..100644
--- a/forum/models/meta.py
+++ b/forum/models/meta.py
diff --git a/forum/models/question.py b/forum/models/question.py
index 4d3154b0..1d387ab7 100755..100644
--- a/forum/models/question.py
+++ b/forum/models/question.py
@@ -96,16 +96,19 @@ class QuestionManager(models.Manager):
params=['%' + search_query + '%']
)
+ #have to import this at run time, otherwise there
+ #a circular import dependency...
+ from forum.conf import settings as forum_settings
if scope_selector:
if scope_selector == 'unanswered':
- if const.UNANSWERED_MEANING == 'NO_ANSWERS':
+ if forum_settings.UNANSWERED_QUESTION_MEANING == 'NO_ANSWERS':
qs = qs.filter(answer_count=0)#todo: expand for different meanings of this
- elif const.UNANSWERED_MEANING == 'NO_ACCEPTED_ANSWERS':
+ elif forum_settings.UNANSWERED_QUESTION_MEANING == 'NO_ACCEPTED_ANSWERS':
qs = qs.filter(answer_accepted=False)
- elif const.UNANSWERED_MEANING == 'NO_UPVOTED_ANSWERS':
+ elif forum_settings.UNANSWERED_QUESTION_MEANING == 'NO_UPVOTED_ANSWERS':
raise NotImplementedError()
else:
- raise Exception('UNANSWERED_MEANING setting is wrong')
+ raise Exception('UNANSWERED_QUESTION_MEANING setting is wrong')
elif scope_selector == 'favorite':
qs = qs.filter(favorited_by = request_user)
@@ -513,7 +516,7 @@ class Question(Content, DeletableContent):
out.append(_('%(people)s commented answers') % {'people':people})
else:
out.append(_('%(people)s commented an answer') % {'people':people})
- url = settings.APP_URL + self.get_absolute_url()
+ url = forum_settings.APP_URL + self.get_absolute_url()
retval = '<a href="%s">%s</a>:<br>\n' % (url,self.title)
out = map(lambda x: '<li>' + x + '</li>',out)
retval += '<ul>' + '\n'.join(out) + '</ul><br>\n'
diff --git a/forum/models/repute.py b/forum/models/repute.py
index f71be4db..f71be4db 100755..100644
--- a/forum/models/repute.py
+++ b/forum/models/repute.py
diff --git a/forum/models/tag.py b/forum/models/tag.py
index e13baf9b..e13baf9b 100755..100644
--- a/forum/models/tag.py
+++ b/forum/models/tag.py
diff --git a/forum/models/user.py b/forum/models/user.py
index 6d871bf4..6d871bf4 100755..100644
--- a/forum/models/user.py
+++ b/forum/models/user.py
diff --git a/forum/modules.py b/forum/modules.py
index 6c9a9dba..6c9a9dba 100755..100644
--- a/forum/modules.py
+++ b/forum/modules.py
diff --git a/forum/search/state_manager.py b/forum/search/state_manager.py
index 6a38a7a2..86cc5662 100644
--- a/forum/search/state_manager.py
+++ b/forum/search/state_manager.py
@@ -2,6 +2,7 @@
#that lives in the session and takes care of the state
#persistece during the search session
from forum import const
+from forum.conf import settings as forum_settings
import logging
ACTIVE_COMMANDS = (
@@ -24,7 +25,7 @@ class SearchState(object):
self.tags = None
self.author = None
self.sort = const.DEFAULT_POST_SORT_METHOD
- self.page_size = const.DEFAULT_QUESTIONS_PAGE_SIZE
+ self.page_size = int(forum_settings.DEFAULT_QUESTIONS_PAGE_SIZE)
self.page = 1
self.logged_in = False
logging.debug('new search state initialized')
diff --git a/forum/settings.py b/forum/settings.py
index 04a7c399..6e31634f 100755..100644
--- a/forum/settings.py
+++ b/forum/settings.py
@@ -1,5 +1,6 @@
+#todo: this file is currently not in use
import os
-
+from livesettings import ConfigurationGroup, IntegerValue, config_register
INSTALLED_APPS = ['forum']
@@ -31,7 +32,7 @@ TEMPLATE_DIRS = [
os.path.join(os.path.dirname(__file__),'skins').replace('\\','/'),
]
-def setup_settings(settings):
+def setup_django_settings(settings):
if (hasattr(settings, 'DEBUG') and getattr(settings, 'DEBUG')):
try:
@@ -48,4 +49,19 @@ def setup_settings(settings):
settings.TEMPLATE_CONTEXT_PROCESSORS = set(settings.TEMPLATE_CONTEXT_PROCESSORS) | set(TEMPLATE_CONTEXT_PROCESSORS)
settings.TEMPLATE_DIRS = set(settings.TEMPLATE_DIRS) | set(TEMPLATE_DIRS)
- \ No newline at end of file
+
+class AskbotConfigGroup(ConfigurationGroup):
+ def __init__(self, key, name, *arg, **kwarg):
+ super(AskbotConfigGroup, self).__init__(key, name, *arg,**kwarg)
+ self.item_count = 0
+ def new_int_setting(self, key, value, description):
+ self.item_count += 1
+ setting = config_register(IntegerValue(
+ self,
+ key,
+ default=value,
+ description=description,
+ ordering=self.item_count
+ )
+ )
+ return setting
diff --git a/forum/sitemap.py b/forum/sitemap.py
index c0c60b5e..c0c60b5e 100755..100644
--- a/forum/sitemap.py
+++ b/forum/sitemap.py
diff --git a/forum/skins/__init__.py b/forum/skins/__init__.py
index 10b6a340..8e5265e8 100755..100644
--- a/forum/skins/__init__.py
+++ b/forum/skins/__init__.py
@@ -1,13 +1,12 @@
-from django.conf import settings
from django.template import loader
from django.template.loaders import filesystem
from django.http import HttpResponse
import os.path
+import os
import logging
#module for skinning askbot
-#at this point skin can be changed only in settings file
-#via ASKBOT_DEFAULT_SKIN variable
+#via ASKBOT_DEFAULT_SKIN configureation variable (not django setting)
#note - Django template loaders use method django.utils._os.safe_join
#to work on unicode file paths
@@ -15,18 +14,32 @@ import logging
def load_template_source(name, dirs=None):
try:
- tname = os.path.join(settings.ASKBOT_DEFAULT_SKIN,'templates',name)
+ #todo: move this to top after splitting out get_skin_dirs()
+ from forum.conf import settings as forum_settings
+ tname = os.path.join(forum_settings.ASKBOT_DEFAULT_SKIN,'templates',name)
return filesystem.load_template_source(tname,dirs)
except:
tname = os.path.join('default','templates',name)
return filesystem.load_template_source(tname,dirs)
load_template_source.is_usable = True
+#todo: move this to skins/utils.py
+#then move import forum.conf.settings to top
+def get_skin_dirs():
+ #todo: handle case of multiple skin directories
+ d = os.path.dirname
+ n = os.path.normpath
+ j = os.path.join
+ f = os.path.isfile
+ skin_dirs = []
+ skin_dirs.append( n(j(d(d(__file__)), 'skins')) )
+ return skin_dirs
+
def find_media_source(url):
"""returns url prefixed with the skin name
of the first skin that contains the file
directories are searched in this order:
- settings.ASKBOT_DEFAULT_SKIN, then 'default', then 'commmon'
+ forum_settings.ASKBOT_DEFAULT_SKIN, then 'default', then 'commmon'
if file is not found - returns None
and logs an error message
"""
@@ -35,11 +48,14 @@ def find_media_source(url):
n = os.path.normpath
j = os.path.join
f = os.path.isfile
- skins = n(j(d(d(__file__)),'skins'))
+ #todo: handles case of multiple skin directories
+ skins = get_skin_dirs()[0]
try:
- media = os.path.join(skins, settings.ASKBOT_DEFAULT_SKIN, url)
+ #todo: move this to top after splitting out get_skin_dirs()
+ from forum.conf import settings as forum_settings
+ media = os.path.join(skins, forum_settings.ASKBOT_DEFAULT_SKIN, url)
assert(f(media))
- use_skin = settings.ASKBOT_DEFAULT_SKIN
+ use_skin = forum_settings.ASKBOT_DEFAULT_SKIN
except:
try:
media = j(skins, 'default', url)
@@ -55,3 +71,20 @@ def find_media_source(url):
use_skin = ''
return None
return use_skin + '/' + url
+
+def get_skin_choices():
+ #todo: expand this to handle custom skin directories
+ dirs = get_skin_dirs()
+ default_dir = dirs[0]
+ items = os.listdir(default_dir)
+ skin_list = ['default']
+ for i in items:
+ item_path = os.path.join(default_dir,i)
+ if not os.path.isdir(item_path):
+ continue
+ if i == 'common':
+ continue
+ if i not in skin_list:
+ skin_list.append(i)
+
+ return [(i,i) for i in skin_list]
diff --git a/forum/skins/default/templates/about.html b/forum/skins/default/templates/about.html
index 686141b3..d091725b 100644
--- a/forum/skins/default/templates/about.html
+++ b/forum/skins/default/templates/about.html
@@ -12,25 +12,7 @@
</div>
<div class="content">
- <p class="strong">Please customize file templates/about.html</p>
-
- <p>Here you can <strong>ask</strong> and <strong>answer</strong> questions, <strong>comment</strong>
- and <strong>vote</strong> for the questions of others and their answers. Both questions and answers
- <strong>can be revised</strong> and improved. Questions can be <strong>tagged</strong> with
- the relevant keywords to simplify future access and organize the accumulated material.
- </p>
-
- <p>This <span class="orange">Q&amp;A</span> site is moderated by its members, hopefully - including yourself!
- Moderation rights are gradually assigned to the site users based on the accumulated <strong>"reputation"</strong>
- points. These points are added to the users account when others vote for his/her questions or answers.
- These points (very) roughly reflect the level of trust of the community.
- </p>
- <p>No points are necessary to ask or answer the questions - so please -
- <strong><a href="{% url user_signin %}">join us!</a></strong>
- </p>
- <p>
- If you would like to find out more about this site - please see <strong><a href="{% url faq %}">frequently asked questions</a></strong>.
- </p>
+ {{settings.FORUM_ABOUT|safe}}
</div>
{% endblock %}
<!-- end template about.html -->
diff --git a/forum/skins/default/templates/ask.html b/forum/skins/default/templates/ask.html
index 4278f4cb..82828fe9 100644
--- a/forum/skins/default/templates/ask.html
+++ b/forum/skins/default/templates/ask.html
@@ -69,7 +69,7 @@
<p>{% trans "login to post question info" %}</p>
</div>
{% else %}
- {% ifequal settings.EMAIL_VALIDATION 'on' %}
+ {% if settings.EMAIL_VALIDATION %}
{% if not request.user.email_isvalid %}
<div class="message">
{% blocktrans with request.user.email as email %}must have valid {{email}} to post,
@@ -77,7 +77,7 @@
{% endblocktrans %}
</div>
{% endif %}
- {% endifequal %}
+ {% endif %}
{% endif %}
<div class="form-item">
<label for="id_title" ><strong>{{ form.title.label_tag }}:</strong></label> <span class="form-error"></span><br/>
diff --git a/forum/skins/default/templates/ask_form.html b/forum/skins/default/templates/ask_form.html
index 25e9fe6c..1bb3866b 100644
--- a/forum/skins/default/templates/ask_form.html
+++ b/forum/skins/default/templates/ask_form.html
@@ -10,13 +10,13 @@
{% if not request.user.is_authenticated %}
<p>{% trans "login to post question info" %}</p>
{% else %}
- {% ifequal settings.EMAIL_VALIDATION 'on' %}
+ {% if settings.EMAIL_VALIDATION %}
{% if not request.user.email_isvalid %}
{% blocktrans with request.user.email as email %}must have valid {{email}} to post,
see {{email_validation_faq_url}}
{% endblocktrans %}
{% endif %}
- {% endifequal %}
+ {% endif %}
{% endif %}
<input id="id_title" class="questionTitleInput" name="title"
value="{% if form.initial.title %}{{form.initial.title}}{% endif %}"/>
diff --git a/forum/skins/default/templates/base.html b/forum/skins/default/templates/base.html
index a4e4ceed..94d3392d 100644
--- a/forum/skins/default/templates/base.html
+++ b/forum/skins/default/templates/base.html
@@ -9,8 +9,12 @@
<title>{% block title %}{% endblock %} - {{ settings.APP_TITLE }}</title>
{% spaceless %}
{% block meta %}{% endblock %}
+ {% block meta_description %}
+ <meta name="description" content="{{settings.APP_DESCRIPTION}}" />
+ {% endblock %}
{% endspaceless %}
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta name="keywords" content="{%block keywords%}{%endblock%},{{settings.APP_KEYWORDS}}" />
{% if settings.GOOGLE_SITEMAP_CODE %}
<meta name="google-site-verification" content="{{settings.GOOGLE_SITEMAP_CODE}}" />
{% endif %}
diff --git a/forum/skins/default/templates/base_content.html b/forum/skins/default/templates/base_content.html
index 284007d8..7b49d9ba 100644
--- a/forum/skins/default/templates/base_content.html
+++ b/forum/skins/default/templates/base_content.html
@@ -6,6 +6,10 @@
<head>
<title>{% block title %}{% endblock %} - {{ settings.APP_TITLE }}</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta name="keywords" content="{%block keywords%}{%endblock%},{{settings.APP_KEYWORDS}}" />
+ {% block meta_description %}
+ <meta name="description" content="{{settings.APP_DESCRIPTION}}" />
+ {% endblock %}
{% if settings.GOOGLE_SITEMAP_CODE %}
<meta name="google-site-verification" content="{{ settings.GOOGLE_SITEMAP_CODE }}" />
{% endif %}
diff --git a/forum/skins/default/templates/faq.html b/forum/skins/default/templates/faq.html
index cc790ccc..c944240a 100644
--- a/forum/skins/default/templates/faq.html
+++ b/forum/skins/default/templates/faq.html
@@ -98,13 +98,13 @@
</table>
</div>
{% comment %}
- {% ifequal settings.EMAIL_VALIDATION 'on' %}
+ {% if settings.EMAIL_VALIDATION %}
<div>
<a id='validate'></a><h3 class="subtitle">{% trans "how to validate email title" %}</h3>
<!--special case here message must contain paragraphs-->
{% blocktrans %}how to validate email info with {{send_email_key_url}} {{gravatar_faq_url}}{% endblocktrans %}
</div>
- {% endifequal %}
+ {% endif %}
{% endcomment %}
<div>
<a id='gravatar'></a><h3 class="subtitle">{% trans "what is gravatar" %}</h3>
diff --git a/forum/skins/default/templates/fbconnect/xd_receiver.html b/forum/skins/default/templates/fbconnect/xd_receiver.html
index a03c61bc..60c02a22 100755
--- a/forum/skins/default/templates/fbconnect/xd_receiver.html
+++ b/forum/skins/default/templates/fbconnect/xd_receiver.html
@@ -2,7 +2,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" >
{% load i18n %}
<head>
- <title>{% blocktrans %}Connect to {{APP_SHORT_NAME}} with Facebook!{% endblocktrans %}
+ <title>{% blocktrans %}Connect to {{settings.APP_SHORT_NAME}} with Facebook!{% endblocktrans %}
</head>
<body>
<script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.js" type="text/javascript"></script>
diff --git a/forum/skins/default/templates/footer.html b/forum/skins/default/templates/footer.html
index 9b7c5d98..a28a1980 100644
--- a/forum/skins/default/templates/footer.html
+++ b/forum/skins/default/templates/footer.html
@@ -23,7 +23,7 @@
<p>
<a href="http://askbot.org" target="_blank">
powered by ASKBOT
- </a>
+ </a><br/>{{settings.APP_COPYRIGHT}}
</p>
</div>
<div id="licenseLogo">
diff --git a/forum/skins/default/templates/header.html b/forum/skins/default/templates/header.html
index 0a1a3296..f2eaa3ac 100644
--- a/forum/skins/default/templates/header.html
+++ b/forum/skins/default/templates/header.html
@@ -2,46 +2,37 @@
{% load extra_tags %}
{% load smart_if %}
{% load i18n %}
- <div id="roof">
- <div id="navBar">
- <div id="top">
- {% if request.user.is_authenticated %}
- <a href="{% url user_profile id=request.user.id,slug=request.user.username|slugify %}">{{ request.user.username }}</a> {% get_score_badge request.user %}
- <a href="{% url logout %}">{% trans "logout" %}</a>
- {% else %}
- <a href="{% url user_signin %}">{% trans "login" %}</a>
- {% endif %}
- <a href="{% url about %}">{% trans "about" %}</a>
- <a href="{% url faq %}">{% trans "faq" %}</a>
- </div>
- <table border="0" cellspacing="0" cellpadding="0">
- <tr>
- <td id="logoContainer">
- <div id="logo">
- <a href="{% url questions %}?start_over=true"><img
- src="{% media "/media/images/logo.gif" %}" title="{% trans "back to home page" %}" alt="{{settings.APP_TITLE}} logo"/></a>
- </div>
- </td>
- <td id="navTabContainer" valign="bottom" align="left">
- <div class="nav">
- <a id="nav_questions" href="{% url questions %}" >{% trans "questions" %}</a>
- <a id="nav_tags" href="{% url tags %}">{% trans "tags" %}</a>
- <a id="nav_users" href="{% url users %}">{% trans "users" %}</a>
- {% if settings.BOOKS_ON %}
- <a id="nav_books" href="{% url books %}">{% trans "books" %}</a>
- {% endif %}
- <a id="nav_badges" href="{% url badges %}">{% trans "badges" %}</a>
- <a id="nav_ask" href="{% url ask %}" class="special">{% trans "ask a question" %}</a>
- {% comment %}
- <a id="nav_unanswered" href="{% url unanswered %}">{% trans "unanswered questions" %}</a>
- <div class="focus">
- <a id="nav_ask" href="{% url ask %}" class="special">{% trans "ask a question" %}</a>
- </div>
- {% endcomment %}
- </div>
- </td>
- </tr>
- </table>
- </div>
- </div>
+<div id="roof">
+ <div id="navBar">
+ <div id="top">
+ {% if request.user.is_authenticated %}
+ <a href="{% url user_profile id=request.user.id,slug=request.user.username|slugify %}">{{ request.user.username }}</a> {% get_score_badge request.user %}
+ <a href="{% url logout %}">{% trans "logout" %}</a>
+ {% else %}
+ <a href="{% url user_signin %}">{% trans "login" %}</a>
+ {% endif %}
+ <a href="{% url about %}">{% trans "about" %}</a>
+ <a href="{% url faq %}">{% trans "faq" %}</a>
+ </div>
+ <table border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <td id="logoContainer">
+ <div id="logo">
+ <a href="{% url questions %}?start_over=true"><img
+ src="{% media "/media/images/logo.gif" %}" title="{% trans "back to home page" %}" alt="{{settings.APP_TITLE}} logo"/></a>
+ </div>
+ </td>
+ <td id="navTabContainer" valign="bottom" align="left">
+ <div class="nav">
+ <a id="nav_questions" href="{% url questions %}" >{% trans "questions" %}</a>
+ <a id="nav_tags" href="{% url tags %}">{% trans "tags" %}</a>
+ <a id="nav_users" href="{% url users %}">{% trans "users" %}</a>
+ <a id="nav_badges" href="{% url badges %}">{% trans "badges" %}</a>
+ <a id="nav_ask" href="{% url ask %}" class="special">{% trans "ask a question" %}</a>
+ </div>
+ </td>
+ </tr>
+ </table>
+ </div>
+</div>
<!-- end template header.html -->
diff --git a/forum/skins/default/templates/privacy.html b/forum/skins/default/templates/privacy.html
index e66086dd..fe074491 100644
--- a/forum/skins/default/templates/privacy.html
+++ b/forum/skins/default/templates/privacy.html
@@ -11,32 +11,7 @@
{% trans "Privacy policy" %}
</div>
<div id="main-body" style="width:100%">
- <p>
- {% trans "general message about privacy" %}
- </p>
-
- <h3 class="subtitle">{% trans "Site Visitors" %}</h3>
- <p>
- {% trans "what technical information is collected about visitors" %}
- </p>
-
- <h3 class="subtitle">{% trans "Personal Information" %}</h3>
- <p>
- {% trans "details on personal information policies" %}
- </p>
-
- <h3 class="subtitle">{% trans "Other Services" %}</h3>
- <p>
- {% trans "details on sharing data with third parties" %}
- </p>
-
- <h3 class="subtitle">Cookies</h3>
- <p>
- {% trans "cookie policy details" %}
- </p>
- <h3 class="subtitle">{% trans "Policy Changes" %}</h3>
- <p>{% trans "how privacy policies can be changed" %}
- </p>
+ {{settings.FORUM_PRIVACY|safe}}
</div>
{% endblock %}
<!-- end privacy.html -->
diff --git a/forum/skins/default/templates/question.html b/forum/skins/default/templates/question.html
index f0619ab8..c75bea84 100644
--- a/forum/skins/default/templates/question.html
+++ b/forum/skins/default/templates/question.html
@@ -7,9 +7,11 @@
{% load i18n %}
{% load cache %}
{% block title %}{% spaceless %}{{ question.get_question_title }}{% endspaceless %}{% endblock %}
-{% block forejs %}
+{% block meta_description %}
<meta name="description" content="{{question.summary}}" />
- <meta name="keywords" content="{{question.tagname_meta_generator}}" />
+{% endblock %}
+{% block keywords %}{{question.tagname_meta_generator}}{% endblock %}
+{% block forejs %}
<link rel="canonical" href="{{settings.APP_URL}}{{question.get_absolute_url}}" />
{% if not question.closed %}
<script type='text/javascript' src='{% media "/media/js/com.cnprog.editor.js" %}'></script>
diff --git a/forum/templatetags/__init__.py b/forum/templatetags/__init__.py
index e69de29b..e69de29b 100755..100644
--- a/forum/templatetags/__init__.py
+++ b/forum/templatetags/__init__.py
diff --git a/forum/templatetags/extra_filters.py b/forum/templatetags/extra_filters.py
index d600c23e..d600c23e 100755..100644
--- a/forum/templatetags/extra_filters.py
+++ b/forum/templatetags/extra_filters.py
diff --git a/forum/templatetags/extra_tags.py b/forum/templatetags/extra_tags.py
index 86f2e9df..39a1ded7 100755..100644
--- a/forum/templatetags/extra_tags.py
+++ b/forum/templatetags/extra_tags.py
@@ -13,6 +13,7 @@ from forum.models import Question, Answer, QuestionRevision, AnswerRevision
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
from django.conf import settings
+from forum.conf import settings as forum_settings
from django.template.defaulttags import url as default_url
from django.template.defaultfilters import slugify
from django.core.urlresolvers import reverse
@@ -146,7 +147,7 @@ def post_contributor_info(post,contributor_type='original_author'):
return {
'post':post,
'post_type':post_type,
- 'wiki_on':settings.WIKI_ON,
+ 'wiki_on':forum_settings.WIKI_ON,
'contributor_type':contributor_type
}
@@ -293,7 +294,10 @@ def media(url):
url = skins.find_media_source(url)
if url:
url = '///' + settings.FORUM_SCRIPT_ALIAS + '/m/' + url
- return posixpath.normpath(url) + '?v=%d' % settings.RESOURCE_REVISION
+ return posixpath.normpath(url) + '?v=%d' \
+ % forum_settings.MEDIA_RESOURCE_REVISION
+ else:
+ return '' #todo: raise exception here?
class ItemSeparatorNode(template.Node):
def __init__(self,separator):
@@ -373,7 +377,7 @@ class BlockMediaUrlNode(template.Node):
url = skins.find_media_source(url)
url = prefix + url
- out = posixpath.normpath(url) + '?v=%d' % settings.RESOURCE_REVISION
+ out = posixpath.normpath(url) + '?v=%d' % forum_settings.MEDIA_RESOURCE_REVISION
return out.replace(' ','')
@register.tag(name='blockmedia')
@@ -395,7 +399,7 @@ class FullUrlNode(template.Node):
self.default_node = default_node
def render(self, context):
- domain = settings.APP_URL
+ domain = forum_settings.APP_URL
#protocol = getattr(settings, "PROTOCOL", "http")
path = self.default_node.render(context)
return "%s%s" % (domain, path)
@@ -407,7 +411,7 @@ def fullurl(parser, token):
@register.simple_tag
def fullmedia(url):
- domain = settings.APP_URL
+ domain = forum_settings.APP_URL
#protocol = getattr(settings, "PROTOCOL", "http")
path = media(url)
return "%s%s" % (domain, path)
@@ -423,39 +427,39 @@ def question_counter_widget(question):
#background and foreground colors for each item
(views_fg, views_bg) = colors.get_counter_colors(
view_count,
- max = settings.VIEW_COUNTER_EXPECTED_MAXIMUM,
- zero_bg = settings.COLORS_VIEW_COUNTER_EMPTY_BG,
- zero_fg = settings.COLORS_VIEW_COUNTER_EMPTY_FG,
- min_bg = settings.COLORS_VIEW_COUNTER_MIN_BG,
- min_fg = settings.COLORS_VIEW_COUNTER_MIN_FG,
- max_bg = settings.COLORS_VIEW_COUNTER_MAX_BG,
- max_fg = settings.COLORS_VIEW_COUNTER_MAX_FG,
+ max = forum_settings.VIEW_COUNTER_EXPECTED_MAXIMUM,
+ zero_bg = forum_settings.COLORS_VIEW_COUNTER_EMPTY_BG,
+ zero_fg = forum_settings.COLORS_VIEW_COUNTER_EMPTY_FG,
+ min_bg = forum_settings.COLORS_VIEW_COUNTER_MIN_BG,
+ min_fg = forum_settings.COLORS_VIEW_COUNTER_MIN_FG,
+ max_bg = forum_settings.COLORS_VIEW_COUNTER_MAX_BG,
+ max_fg = forum_settings.COLORS_VIEW_COUNTER_MAX_FG,
)
(answers_fg, answers_bg) = colors.get_counter_colors(
answer_count,
- max = settings.ANSWER_COUNTER_EXPECTED_MAXIMUM,
- zero_bg = settings.COLORS_ANSWER_COUNTER_EMPTY_BG,
- zero_fg = settings.COLORS_ANSWER_COUNTER_EMPTY_FG,
- min_bg = settings.COLORS_ANSWER_COUNTER_MIN_BG,
- min_fg = settings.COLORS_ANSWER_COUNTER_MIN_FG,
- max_bg = settings.COLORS_ANSWER_COUNTER_MAX_BG,
- max_fg = settings.COLORS_ANSWER_COUNTER_MAX_FG,
+ max = forum_settings.ANSWER_COUNTER_EXPECTED_MAXIMUM,
+ zero_bg = forum_settings.COLORS_ANSWER_COUNTER_EMPTY_BG,
+ zero_fg = forum_settings.COLORS_ANSWER_COUNTER_EMPTY_FG,
+ min_bg = forum_settings.COLORS_ANSWER_COUNTER_MIN_BG,
+ min_fg = forum_settings.COLORS_ANSWER_COUNTER_MIN_FG,
+ max_bg = forum_settings.COLORS_ANSWER_COUNTER_MAX_BG,
+ max_fg = forum_settings.COLORS_ANSWER_COUNTER_MAX_FG,
)
if answer_accepted:
#todo: maybe recalculate the foreground color too
- answers_bg = settings.COLORS_ANSWER_COUNTER_ACCEPTED_BG
- answers_fg = settings.COLORS_ANSWER_COUNTER_ACCEPTED_FG
+ answers_bg = forum_settings.COLORS_ANSWER_COUNTER_ACCEPTED_BG
+ answers_fg = forum_settings.COLORS_ANSWER_COUNTER_ACCEPTED_FG
(votes_fg, votes_bg) = colors.get_counter_colors(
vote_count,
- max = settings.VOTE_COUNTER_EXPECTED_MAXIMUM,
- zero_bg = settings.COLORS_VOTE_COUNTER_EMPTY_BG,
- zero_fg = settings.COLORS_VOTE_COUNTER_EMPTY_FG,
- min_bg = settings.COLORS_VOTE_COUNTER_MIN_BG,
- min_fg = settings.COLORS_VOTE_COUNTER_MIN_FG,
- max_bg = settings.COLORS_VOTE_COUNTER_MAX_BG,
- max_fg = settings.COLORS_VOTE_COUNTER_MAX_FG,
+ max = forum_settings.VOTE_COUNTER_EXPECTED_MAXIMUM,
+ zero_bg = forum_settings.COLORS_VOTE_COUNTER_EMPTY_BG,
+ zero_fg = forum_settings.COLORS_VOTE_COUNTER_EMPTY_FG,
+ min_bg = forum_settings.COLORS_VOTE_COUNTER_MIN_BG,
+ min_fg = forum_settings.COLORS_VOTE_COUNTER_MIN_FG,
+ max_bg = forum_settings.COLORS_VOTE_COUNTER_MAX_BG,
+ max_fg = forum_settings.COLORS_VOTE_COUNTER_MAX_FG,
)
#returns a dictionary with keys like 'votes_bg', etc
diff --git a/forum/templatetags/smart_if.py b/forum/templatetags/smart_if.py
index ca3b43fe..ca3b43fe 100755..100644
--- a/forum/templatetags/smart_if.py
+++ b/forum/templatetags/smart_if.py
diff --git a/forum/urls.py b/forum/urls.py
index 41ffbcf7..41ffbcf7 100755..100644
--- a/forum/urls.py
+++ b/forum/urls.py
diff --git a/forum/user_messages/__init__.py b/forum/user_messages/__init__.py
index 0136c888..0136c888 100755..100644
--- a/forum/user_messages/__init__.py
+++ b/forum/user_messages/__init__.py
diff --git a/forum/user_messages/context_processors.py b/forum/user_messages/context_processors.py
index 5f7b857c..5f7b857c 100755..100644
--- a/forum/user_messages/context_processors.py
+++ b/forum/user_messages/context_processors.py
diff --git a/forum/utils/__init__.py b/forum/utils/__init__.py
index e69de29b..e69de29b 100755..100644
--- a/forum/utils/__init__.py
+++ b/forum/utils/__init__.py
diff --git a/forum/utils/cache.py b/forum/utils/cache.py
index 6341392e..6341392e 100755..100644
--- a/forum/utils/cache.py
+++ b/forum/utils/cache.py
diff --git a/forum/utils/decorators.py b/forum/utils/decorators.py
index 440e8312..440e8312 100755..100644
--- a/forum/utils/decorators.py
+++ b/forum/utils/decorators.py
diff --git a/forum/utils/diff.py b/forum/utils/diff.py
index d741d788..d741d788 100755..100644
--- a/forum/utils/diff.py
+++ b/forum/utils/diff.py
diff --git a/forum/utils/email.py b/forum/utils/email.py
index dc712572..dc712572 100755..100644
--- a/forum/utils/email.py
+++ b/forum/utils/email.py
diff --git a/forum/utils/forms.py b/forum/utils/forms.py
index d8d04d05..946c1fd9 100755..100644
--- a/forum/utils/forms.py
+++ b/forum/utils/forms.py
@@ -3,6 +3,7 @@ import re
from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe
from django.conf import settings
+from forum.conf import settings as forum_settings
from django.http import str_to_unicode
from django.contrib.auth.models import User
import logging
@@ -121,7 +122,7 @@ class UserEmailField(forms.EmailField):
email = super(UserEmailField,self).clean(email.strip())
if self.skip_clean:
return email
- if settings.EMAIL_UNIQUE == True:
+ if forum_settings.EMAIL_UNIQUE == True:
try:
user = User.objects.get(email = email)
logging.debug('email taken')
diff --git a/forum/utils/html.py b/forum/utils/html.py
index 25a74a4a..25a74a4a 100755..100644
--- a/forum/utils/html.py
+++ b/forum/utils/html.py
diff --git a/forum/utils/lists.py b/forum/utils/lists.py
index f69c8f20..f69c8f20 100755..100644
--- a/forum/utils/lists.py
+++ b/forum/utils/lists.py
diff --git a/forum/utils/odict.py b/forum/utils/odict.py
index 2c8391d7..2c8391d7 100755..100644
--- a/forum/utils/odict.py
+++ b/forum/utils/odict.py
diff --git a/forum/utils/time.py b/forum/utils/time.py
index 39e01d0f..39e01d0f 100755..100644
--- a/forum/utils/time.py
+++ b/forum/utils/time.py
diff --git a/forum/views/__init__.py b/forum/views/__init__.py
index 291fee2a..291fee2a 100755..100644
--- a/forum/views/__init__.py
+++ b/forum/views/__init__.py
diff --git a/forum/views/commands.py b/forum/views/commands.py
index 39b34d13..4746f71d 100755..100644
--- a/forum/views/commands.py
+++ b/forum/views/commands.py
@@ -1,5 +1,7 @@
import datetime
+#todo: maybe eliminate usage of django.settings
from django.conf import settings
+from forum.conf import settings as forum_settings
from django.utils import simplejson
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render_to_response
@@ -138,7 +140,8 @@ def vote(request, id):#todo: pretty incomprehensible view used by various ajax c
vote = post.votes.filter(user=request.user)[0]
# get latest vote by the current user
# unvote should be less than certain time
- if (datetime.datetime.now().day - vote.voted_at.day) >= auth.VOTE_RULES['scope_deny_unvote_days']:
+ if (datetime.datetime.now().day - vote.voted_at.day) \
+ >= forum_settings.MAX_DAYS_TO_CANCEL_VOTE:
response_data['status'] = 2
else:
voted = vote.vote
@@ -152,7 +155,8 @@ def vote(request, id):#todo: pretty incomprehensible view used by various ajax c
response_data['status'] = 1
response_data['count'] = post.score
- elif Vote.objects.get_votes_count_today_from_user(request.user) >= auth.VOTE_RULES['scope_votes_per_user_per_day']:
+ elif Vote.objects.get_votes_count_today_from_user(request.user)\
+ >= forum_settings.MAX_VOTES_PER_USER_PER_DAY:
response_data['allowed'] = -3
else:
vote = Vote(user=request.user, content_object=post, vote=vote_score, voted_at=datetime.datetime.now())
@@ -163,8 +167,10 @@ def vote(request, id):#todo: pretty incomprehensible view used by various ajax c
# downvote
auth.onDownVoted(vote, post, request.user)
- votes_left = auth.VOTE_RULES['scope_votes_per_user_per_day'] - Vote.objects.get_votes_count_today_from_user(request.user)
- if votes_left <= auth.VOTE_RULES['scope_warn_votes_left']:
+ votes_left = forum_settings.MAX_VOTES_PER_USER_PER_DAY \
+ - Vote.objects.get_votes_count_today_from_user(request.user)
+ if votes_left <= \
+ forum_settings.VOTES_LEFT_WARNING_THRESHOLD:
response_data['message'] = u'%s votes left' % votes_left
response_data['count'] = post.score
elif vote_type in ['7', '8']:
@@ -174,7 +180,7 @@ def vote(request, id):#todo: pretty incomprehensible view used by various ajax c
post_id = request.POST.get('postId')
post = get_object_or_404(Answer, id=post_id)
- if FlaggedItem.objects.get_flagged_items_count_today(request.user) >= auth.VOTE_RULES['scope_flags_per_user_per_day']:
+ if FlaggedItem.objects.get_flagged_items_count_today(request.user) >= forum_settings.MAX_FLAGS_PER_USER_PER_DAY:
response_data['allowed'] = -3
elif not auth.can_flag_offensive(request.user):
response_data['allowed'] = -2
@@ -204,7 +210,9 @@ def vote(request, id):#todo: pretty incomprehensible view used by various ajax c
if user.is_authenticated():
if user not in question.followed_by.all():
question.followed_by.add(user)
- if settings.EMAIL_VALIDATION == 'on' and user.email_isvalid == False:
+ if forum_settings.EMAIL_VALIDATION == True \
+ and user.email_isvalid == False:
+
response_data['message'] = \
_('subscription saved, %(email)s needs validation, see %(details_url)s') \
% {'email':user.email,'details_url':reverse('faq') + '#validate'}
diff --git a/forum/views/meta.py b/forum/views/meta.py
index af5fe6df..af5fe6df 100755..100644
--- a/forum/views/meta.py
+++ b/forum/views/meta.py
diff --git a/forum/views/readers.py b/forum/views/readers.py
index cd3832d3..f75f62a4 100644
--- a/forum/views/readers.py
+++ b/forum/views/readers.py
@@ -21,14 +21,13 @@ from markdown2 import Markdown
from forum.utils.diff import textDiff as htmldiff
from forum.forms import *
from forum.models import *
-from forum.auth import *
from forum import const
from forum import auth
from forum.utils.forms import get_next_url
from forum.search.state_manager import SearchState
# used in index page
-#refactor - move these numbers somewhere?
+#todo: - take these out of const or settings
INDEX_PAGE_SIZE = 30
INDEX_AWARD_SIZE = 15
INDEX_TAGS_SIZE = 25
diff --git a/forum/views/users.py b/forum/views/users.py
index 76e41008..8a7ecaa6 100755..100644
--- a/forum/views/users.py
+++ b/forum/views/users.py
@@ -19,6 +19,7 @@ from django.contrib.contenttypes.models import ContentType
from forum.models import user_updated
from forum.const import USERS_PAGE_SIZE
from django.conf import settings
+from forum.conf import settings as forum_settings
question_type = ContentType.objects.get_for_model(Question)
answer_type = ContentType.objects.get_for_model(Answer)
@@ -108,7 +109,7 @@ def set_new_email(user, new_email, nomessage=False):
user.email = new_email
user.email_isvalid = False
user.save()
- #if settings.EMAIL_VALIDATION == 'on':
+ #if forum_settings.EMAIL_VALIDATION == True:
# send_new_email_key(user,nomessage=nomessage)
@login_required
@@ -123,7 +124,7 @@ def edit_user(request, id):
set_new_email(user, new_email)
- if settings.EDITABLE_SCREEN_NAME:
+ if forum_settings.EDITABLE_SCREEN_NAME:
user.username = sanitize_html(form.cleaned_data['username'])
user.real_name = sanitize_html(form.cleaned_data['realname'])
@@ -218,7 +219,7 @@ def user_stats(request, user_id, user_view):
up_votes = Vote.objects.get_up_vote_count_from_user(user)
down_votes = Vote.objects.get_down_vote_count_from_user(user)
votes_today = Vote.objects.get_votes_count_today_from_user(user)
- votes_total = auth.VOTE_RULES['scope_votes_per_user_per_day']
+ votes_total = forum_settings.MAX_VOTES_PER_USER_PER_DAY
question_id_set = set(map(lambda v: v['id'], list(questions))) \
| set(map(lambda v: v['id'], list(answered_questions)))
diff --git a/forum/views/writers.py b/forum/views/writers.py
index 86831ba3..eddf2a87 100755..100644
--- a/forum/views/writers.py
+++ b/forum/views/writers.py
@@ -15,7 +15,6 @@ from django.core.exceptions import PermissionDenied
from forum.forms import *
from forum.models import *
-from forum.auth import *
from forum.const import *
from forum import auth
from forum.utils.forms import get_next_url
diff --git a/forum_modules/__init__.py b/forum_modules/__init__.py
index e69de29b..e69de29b 100755..100644
--- a/forum_modules/__init__.py
+++ b/forum_modules/__init__.py
diff --git a/forum_modules/authentication/auth.py b/forum_modules/authentication/auth.py
index 96025dc1..96025dc1 100755..100644
--- a/forum_modules/authentication/auth.py
+++ b/forum_modules/authentication/auth.py
diff --git a/forum_modules/books/__init__.py b/forum_modules/books/__init__.py
index c51a2bfb..c51a2bfb 100755..100644
--- a/forum_modules/books/__init__.py
+++ b/forum_modules/books/__init__.py
diff --git a/forum_modules/books/models.py b/forum_modules/books/models.py
index a78c0e76..a78c0e76 100755..100644
--- a/forum_modules/books/models.py
+++ b/forum_modules/books/models.py
diff --git a/forum_modules/books/urls.py b/forum_modules/books/urls.py
index bc0811e7..bc0811e7 100755..100644
--- a/forum_modules/books/urls.py
+++ b/forum_modules/books/urls.py
diff --git a/forum_modules/books/views.py b/forum_modules/books/views.py
index d4907e5f..d4907e5f 100755..100644
--- a/forum_modules/books/views.py
+++ b/forum_modules/books/views.py
diff --git a/forum_modules/robotstxt/__init__.py b/forum_modules/robotstxt/__init__.py
index e69de29b..e69de29b 100755..100644
--- a/forum_modules/robotstxt/__init__.py
+++ b/forum_modules/robotstxt/__init__.py
diff --git a/forum_modules/robotstxt/urls.py b/forum_modules/robotstxt/urls.py
index 79a6d84c..79a6d84c 100755..100644
--- a/forum_modules/robotstxt/urls.py
+++ b/forum_modules/robotstxt/urls.py
diff --git a/keyedcache/__init__.py b/keyedcache/__init__.py
new file mode 100644
index 00000000..d7dfe9ec
--- /dev/null
+++ b/keyedcache/__init__.py
@@ -0,0 +1,329 @@
+"""A full cache system written on top of Django's rudimentary one."""
+
+from django.conf import settings
+from django.core.cache import cache
+from django.utils.encoding import smart_str
+from django.utils.hashcompat import md5_constructor
+from keyedcache.utils import is_string_like, is_list_or_tuple
+import cPickle as pickle
+import logging
+import types
+
+log = logging.getLogger('keyedcache')
+
+CACHED_KEYS = {}
+CACHE_CALLS = 0
+CACHE_HITS = 0
+KEY_DELIM = "::"
+REQUEST_CACHE = {'enabled' : False}
+try:
+ CACHE_PREFIX = settings.CACHE_PREFIX
+except AttributeError:
+ CACHE_PREFIX = str(settings.SITE_ID)
+ log.warn("No CACHE_PREFIX found in settings, using SITE_ID. Please update your settings to add a CACHE_PREFIX")
+
+try:
+ CACHE_TIMEOUT = settings.CACHE_TIMEOUT
+except AttributeError:
+ CACHE_TIMEOUT = 0
+ log.warn("No CACHE_TIMEOUT found in settings, so we used 0, disabling the cache system. Please update your settings to add a CACHE_TIMEOUT and avoid this warning.")
+
+_CACHE_ENABLED = CACHE_TIMEOUT > 0
+
+class CacheWrapper(object):
+ def __init__(self, val, inprocess=False):
+ self.val = val
+ self.inprocess = inprocess
+
+ def __str__(self):
+ return str(self.val)
+
+ def __repr__(self):
+ return repr(self.val)
+
+ def wrap(cls, obj):
+ if isinstance(obj, cls):
+ return obj
+ else:
+ return cls(obj)
+
+ wrap = classmethod(wrap)
+
+class MethodNotFinishedError(Exception):
+ def __init__(self, f):
+ self.func = f
+
+
+class NotCachedError(Exception):
+ def __init__(self, k):
+ self.key = k
+
+class CacheNotRespondingError(Exception):
+ pass
+
+def cache_delete(*keys, **kwargs):
+ removed = []
+ if cache_enabled():
+ global CACHED_KEYS
+ log.debug('cache_delete')
+ children = kwargs.pop('children',False)
+
+ if (keys or kwargs):
+ key = cache_key(*keys, **kwargs)
+
+ if CACHED_KEYS.has_key(key):
+ del CACHED_KEYS[key]
+ removed.append(key)
+
+ cache.delete(key)
+
+ if children:
+ key = key + KEY_DELIM
+ children = [x for x in CACHED_KEYS.keys() if x.startswith(key)]
+ for k in children:
+ del CACHED_KEYS[k]
+ cache.delete(k)
+ removed.append(k)
+ else:
+ key = "All Keys"
+ deleteneeded = _cache_flush_all()
+
+ removed = CACHED_KEYS.keys()
+
+ if deleteneeded:
+ for k in CACHED_KEYS:
+ cache.delete(k)
+
+ CACHED_KEYS = {}
+
+ if removed:
+ log.debug("Cache delete: %s", removed)
+ else:
+ log.debug("No cached objects to delete for %s", key)
+
+ return removed
+
+
+def cache_delete_function(func):
+ return cache_delete(['func', func.__name__, func.__module__], children=True)
+
+def cache_enabled():
+ global _CACHE_ENABLED
+ return _CACHE_ENABLED
+
+def cache_enable(state=True):
+ global _CACHE_ENABLED
+ _CACHE_ENABLED=state
+
+def _cache_flush_all():
+ if is_memcached_backend():
+ cache._cache.flush_all()
+ return False
+ return True
+
+def cache_function(length=CACHE_TIMEOUT):
+ """
+ A variant of the snippet posted by Jeff Wheeler at
+ http://www.djangosnippets.org/snippets/109/
+
+ Caches a function, using the function and its arguments as the key, and the return
+ value as the value saved. It passes all arguments on to the function, as
+ it should.
+
+ The decorator itself takes a length argument, which is the number of
+ seconds the cache will keep the result around.
+
+ It will put a temp value in the cache while the function is
+ processing. This should not matter in most cases, but if the app is using
+ threads, you won't be able to get the previous value, and will need to
+ wait until the function finishes. If this is not desired behavior, you can
+ remove the first two lines after the ``else``.
+ """
+ def decorator(func):
+ def inner_func(*args, **kwargs):
+ if not cache_enabled():
+ value = func(*args, **kwargs)
+
+ else:
+ try:
+ value = cache_get('func', func.__name__, func.__module__, args, kwargs)
+
+ except NotCachedError, e:
+ # This will set a temporary value while ``func`` is being
+ # processed. When using threads, this is vital, as otherwise
+ # the function can be called several times before it finishes
+ # and is put into the cache.
+ funcwrapper = CacheWrapper(".".join([func.__module__, func.__name__]), inprocess=True)
+ cache_set(e.key, value=funcwrapper, length=length, skiplog=True)
+ value = func(*args, **kwargs)
+ cache_set(e.key, value=value, length=length)
+
+ except MethodNotFinishedError, e:
+ value = func(*args, **kwargs)
+
+ return value
+ return inner_func
+ return decorator
+
+
+def cache_get(*keys, **kwargs):
+ if kwargs.has_key('default'):
+ default_value = kwargs.pop('default')
+ use_default = True
+ else:
+ use_default = False
+
+ key = cache_key(keys, **kwargs)
+
+ if not cache_enabled():
+ raise NotCachedError(key)
+ else:
+ global CACHE_CALLS, CACHE_HITS, REQUEST_CACHE
+ CACHE_CALLS += 1
+ if CACHE_CALLS == 1:
+ cache_require()
+
+ obj = None
+ tid = -1
+ if REQUEST_CACHE['enabled']:
+ tid = cache_get_request_uid()
+ if tid > -1:
+ try:
+ obj = REQUEST_CACHE[tid][key]
+ log.debug('Got from request cache: %s', key)
+ except KeyError:
+ pass
+
+ if obj == None:
+ obj = cache.get(key)
+
+ if obj and isinstance(obj, CacheWrapper):
+ CACHE_HITS += 1
+ CACHED_KEYS[key] = True
+ log.debug('got cached [%i/%i]: %s', CACHE_CALLS, CACHE_HITS, key)
+ if obj.inprocess:
+ raise MethodNotFinishedError(obj.val)
+
+ cache_set_request(key, obj, uid=tid)
+
+ return obj.val
+ else:
+ try:
+ del CACHED_KEYS[key]
+ except KeyError:
+ pass
+
+ if use_default:
+ return default_value
+
+ raise NotCachedError(key)
+
+
+def cache_set(*keys, **kwargs):
+ """Set an object into the cache."""
+ if cache_enabled():
+ global CACHED_KEYS, REQUEST_CACHE
+ obj = kwargs.pop('value')
+ length = kwargs.pop('length', CACHE_TIMEOUT)
+ skiplog = kwargs.pop('skiplog', False)
+
+ key = cache_key(keys, **kwargs)
+ val = CacheWrapper.wrap(obj)
+ if not skiplog:
+ log.debug('setting cache: %s', key)
+ cache.set(key, val, length)
+ CACHED_KEYS[key] = True
+ if REQUEST_CACHE['enabled']:
+ cache_set_request(key, val)
+
+def _hash_or_string(key):
+ if is_string_like(key) or isinstance(key, (types.IntType, types.LongType, types.FloatType)):
+ return smart_str(key)
+ else:
+ try:
+ #if it has a PK, use it.
+ return str(key._get_pk_val())
+ except AttributeError:
+ return md5_hash(key)
+
+def cache_contains(*keys, **kwargs):
+ key = cache_key(keys, **kwargs)
+ return CACHED_KEYS.has_key(key)
+
+def cache_key(*keys, **pairs):
+ """Smart key maker, returns the object itself if a key, else a list
+ delimited by ':', automatically hashing any non-scalar objects."""
+
+ if is_string_like(keys):
+ keys = [keys]
+
+ if is_list_or_tuple(keys):
+ if len(keys) == 1 and is_list_or_tuple(keys[0]):
+ keys = keys[0]
+ else:
+ keys = [md5_hash(keys)]
+
+ if pairs:
+ keys = list(keys)
+ klist = pairs.keys()
+ klist.sort()
+ for k in klist:
+ keys.append(k)
+ keys.append(pairs[k])
+
+ key = KEY_DELIM.join([_hash_or_string(x) for x in keys])
+ prefix = CACHE_PREFIX + KEY_DELIM
+ if not key.startswith(prefix):
+ key = prefix+key
+ return key.replace(" ", ".")
+
+def md5_hash(obj):
+ pickled = pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL)
+ return md5_constructor(pickled).hexdigest()
+
+
+def is_memcached_backend():
+ try:
+ return cache._cache.__module__.endswith('memcache')
+ except AttributeError:
+ return False
+
+def cache_require():
+ """Error if keyedcache isn't running."""
+ if cache_enabled():
+ key = cache_key('require_cache')
+ cache_set(key,value='1')
+ v = cache_get(key, default = '0')
+ if v != '1':
+ raise CacheNotRespondingError()
+ else:
+ log.debug("Cache responding OK")
+ return True
+
+def cache_clear_request(uid):
+ """Clears all locally cached elements with that uid"""
+ global REQUEST_CACHE
+ try:
+ del REQUEST_CACHE[uid]
+ log.debug('cleared request cache: %s', uid)
+ except KeyError:
+ pass
+
+def cache_use_request_caching():
+ global REQUEST_CACHE
+ REQUEST_CACHE['enabled'] = True
+
+def cache_get_request_uid():
+ from threaded_multihost import threadlocals
+ return threadlocals.get_thread_variable('request_uid', -1)
+
+def cache_set_request(key, val, uid=None):
+ if uid == None:
+ uid = cache_get_request_uid()
+
+ if uid>-1:
+ global REQUEST_CACHE
+ if not uid in REQUEST_CACHE:
+ REQUEST_CACHE[uid] = {key:val}
+ else:
+ REQUEST_CACHE[uid][key] = val
diff --git a/keyedcache/locale/de/LC_MESSAGES/django.mo b/keyedcache/locale/de/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..c623451a
--- /dev/null
+++ b/keyedcache/locale/de/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/de/LC_MESSAGES/django.po b/keyedcache/locale/de/LC_MESSAGES/django.po
new file mode 100644
index 00000000..fc94969b
--- /dev/null
+++ b/keyedcache/locale/de/LC_MESSAGES/django.po
@@ -0,0 +1,40 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the Satchmo package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-03-22 15:10+0100\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"
+
+#: templates/keyedcache/delete.html:6
+#: templates/keyedcache/stats.html:6
+#: templates/keyedcache/view.html:6
+msgid "Home"
+msgstr "Start"
+
+#: templates/keyedcache/delete.html:7
+#: templates/keyedcache/view.html:7
+msgid "Cache"
+msgstr ""
+
+#: templates/keyedcache/delete.html:8
+msgid "Cache Delete"
+msgstr ""
+
+#: templates/keyedcache/stats.html:7
+msgid "Cache Stats"
+msgstr "Cachestatistik"
+
+#: templates/keyedcache/view.html:8
+msgid "Cache View"
+msgstr ""
+
diff --git a/keyedcache/locale/en/LC_MESSAGES/django.mo b/keyedcache/locale/en/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..c2bc0b94
--- /dev/null
+++ b/keyedcache/locale/en/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/en/LC_MESSAGES/django.po b/keyedcache/locale/en/LC_MESSAGES/django.po
new file mode 100644
index 00000000..7c6fdf87
--- /dev/null
+++ b/keyedcache/locale/en/LC_MESSAGES/django.po
@@ -0,0 +1,40 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the Satchmo package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-12-31 00:49-0600\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"
+
+#: templates/keyedcache/delete.html:6
+#: templates/keyedcache/stats.html:6
+#: templates/keyedcache/view.html:6
+msgid "Home"
+msgstr ""
+
+#: templates/keyedcache/delete.html:7
+#: templates/keyedcache/view.html:7
+msgid "Cache"
+msgstr ""
+
+#: templates/keyedcache/delete.html:8
+msgid "Cache Delete"
+msgstr ""
+
+#: templates/keyedcache/stats.html:7
+msgid "Cache Stats"
+msgstr ""
+
+#: templates/keyedcache/view.html:8
+msgid "Cache View"
+msgstr ""
+
diff --git a/keyedcache/locale/es/LC_MESSAGES/django.po b/keyedcache/locale/es/LC_MESSAGES/django.po
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/keyedcache/locale/es/LC_MESSAGES/django.po
diff --git a/keyedcache/locale/fr/LC_MESSAGES/django.mo b/keyedcache/locale/fr/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..43ae9859
--- /dev/null
+++ b/keyedcache/locale/fr/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/fr/LC_MESSAGES/django.po b/keyedcache/locale/fr/LC_MESSAGES/django.po
new file mode 100644
index 00000000..811a3def
--- /dev/null
+++ b/keyedcache/locale/fr/LC_MESSAGES/django.po
@@ -0,0 +1,69 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# Jacques Moulin <jacques@tpi.be>, 2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-11-02 16:11+0100\n"
+"PO-Revision-Date: 2008-11-02 17:51+0100\n"
+"Last-Translator: Jacques Moulin <jacques@tpi.be>\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"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Poedit-Language: French\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+#: views.py:16
+msgid "Yes"
+msgstr "Oui"
+
+#: views.py:17
+msgid "No"
+msgstr "Non"
+
+#: views.py:21
+msgid "Key to delete"
+msgstr "Clé à effacer"
+
+#: views.py:22
+msgid "Include Children?"
+msgstr "Inclure les enfants?"
+
+#: views.py:23
+msgid "Delete all keys?"
+msgstr "Effacer toutes les clés?"
+
+#: templates/keyedcache/delete.html.py:6
+#: templates/keyedcache/stats.html.py:6
+#: templates/keyedcache/view.html.py:6
+#: templates/keyedcache/delete.html.py:6
+#: templates/keyedcache/stats.html.py:6
+#: templates/keyedcache/view.html.py:6
+msgid "Home"
+msgstr "Accueil"
+
+#: templates/keyedcache/delete.html.py:7
+#: templates/keyedcache/view.html.py:7
+#: templates/keyedcache/delete.html.py:7
+#: templates/keyedcache/view.html.py:7
+msgid "Cache"
+msgstr "Cache"
+
+#: templates/keyedcache/delete.html.py:8
+#: templates/keyedcache/delete.html.py:8
+msgid "Cache Delete"
+msgstr "Vider le cache"
+
+#: templates/keyedcache/stats.html.py:7
+#: templates/keyedcache/stats.html.py:7
+msgid "Cache Stats"
+msgstr "Statut du cache"
+
+#: templates/keyedcache/view.html.py:8
+#: templates/keyedcache/view.html.py:8
+msgid "Cache View"
+msgstr "Voir le cache"
+
diff --git a/keyedcache/locale/he/LC_MESSAGES/django.mo b/keyedcache/locale/he/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..8c043f31
--- /dev/null
+++ b/keyedcache/locale/he/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/he/LC_MESSAGES/django.po b/keyedcache/locale/he/LC_MESSAGES/django.po
new file mode 100644
index 00000000..7354e16a
--- /dev/null
+++ b/keyedcache/locale/he/LC_MESSAGES/django.po
@@ -0,0 +1,60 @@
+# translation of Satchmo
+# Copyright (C) 2008 The Satchmo Project
+# This file is distributed under the same license as the Satchmo package.
+#
+# Aviv Greenberg <avivgr@gmail.com>, 2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: django\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-03-13 23:01+0200\n"
+"PO-Revision-Date: 2009-03-13 16:04\n"
+"Last-Translator: Aviv Greenberg <avivgr@gmail.com>\n"
+"Language-Team: <en@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: KBabel 1.11.4\n"
+"X-Translated-Using: django-rosetta 0.4.0\n"
+
+#: views.py:16
+msgid "Yes"
+msgstr "כן"
+
+#: views.py:17
+msgid "No"
+msgstr "לא"
+
+#: views.py:21
+msgid "Key to delete"
+msgstr "מפתח שיש למחוק"
+
+#: views.py:22
+msgid "Include Children?"
+msgstr "כלול ילדים?"
+
+#: views.py:23
+msgid "Delete all keys?"
+msgstr "מחק את כל המפתחות?"
+
+#: templates/keyedcache/delete.html:6 templates/keyedcache/stats.html:6
+#: templates/keyedcache/view.html:6
+msgid "Home"
+msgstr "בית"
+
+#: templates/keyedcache/delete.html:7 templates/keyedcache/view.html:7
+msgid "Cache"
+msgstr "זכרון מטמון"
+
+#: templates/keyedcache/delete.html:8
+msgid "Cache Delete"
+msgstr "מחק זכרון מטמון"
+
+#: templates/keyedcache/stats.html:7
+msgid "Cache Stats"
+msgstr "סטטיסטיקת זכרון מטמון"
+
+#: templates/keyedcache/view.html:8
+msgid "Cache View"
+msgstr "הצג זכרון מטמון"
diff --git a/keyedcache/locale/it/LC_MESSAGES/django.mo b/keyedcache/locale/it/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..5e614a26
--- /dev/null
+++ b/keyedcache/locale/it/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/it/LC_MESSAGES/django.po b/keyedcache/locale/it/LC_MESSAGES/django.po
new file mode 100644
index 00000000..271ef7be
--- /dev/null
+++ b/keyedcache/locale/it/LC_MESSAGES/django.po
@@ -0,0 +1,68 @@
+# translation of django.po to Italiano
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the PACKAGE package.
+#
+# costantino giuliodori <costantino.giuliodori@gmail.com>, 2007.
+# Alessandro Ronchi <alessandro.ronchi@soasi.com>, 2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: django\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-09-27 09:16-0700\n"
+"PO-Revision-Date: 2008-09-30 13:13+0200\n"
+"Last-Translator: Alessandro Ronchi <alessandro.ronchi@soasi.com>\n"
+"Language-Team: Italiano <it@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: KBabel 1.11.4\n"
+"Plural-Forms: nplurals=2; plural=n > 1\n"
+
+#: views.py:16
+msgid "Yes"
+msgstr "Si"
+
+#: views.py:17
+msgid "No"
+msgstr "No"
+
+#: views.py:21
+msgid "Key to delete"
+msgstr "Chiave da eliminare"
+
+#: views.py:22
+# translated = "Slug"
+msgid "Include Children?"
+msgstr "Includi i figli?"
+
+#: views.py:23
+msgid "Delete all keys?"
+msgstr "Elimina tutte le chiavi?"
+
+#: templates/keyedcache/delete.html:6
+#: templates/keyedcache/stats.html:6
+#: templates/keyedcache/view.html:6
+msgid "Home"
+msgstr "Pagina iniziale"
+
+#: templates/keyedcache/delete.html:7
+#: templates/keyedcache/view.html:7
+# translated = "Prodotto sottotipi"
+msgid "Cache"
+msgstr "Cache"
+
+#: templates/keyedcache/delete.html:8
+# translated = "Cache"
+msgid "Cache Delete"
+msgstr "Elimina Cache"
+
+#: templates/keyedcache/stats.html:7
+# translated = "Elimina cache"
+msgid "Cache Stats"
+msgstr "Statistiche Cache"
+
+#: templates/keyedcache/view.html:8
+# translated = "Statistiche di cache"
+msgid "Cache View"
+msgstr "Viste Cache"
+
diff --git a/keyedcache/locale/ko/LC_MESSAGES/django.mo b/keyedcache/locale/ko/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..18d97529
--- /dev/null
+++ b/keyedcache/locale/ko/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/ko/LC_MESSAGES/django.po b/keyedcache/locale/ko/LC_MESSAGES/django.po
new file mode 100644
index 00000000..0969979a
--- /dev/null
+++ b/keyedcache/locale/ko/LC_MESSAGES/django.po
@@ -0,0 +1,40 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the Satchmo package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-12-31 00:49-0600\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"
+
+#: templates/keyedcache/delete.html:6
+#: templates/keyedcache/stats.html:6
+#: templates/keyedcache/view.html:6
+msgid "Home"
+msgstr "홈"
+
+#: templates/keyedcache/delete.html:7
+#: templates/keyedcache/view.html:7
+msgid "Cache"
+msgstr "캐쉬"
+
+#: templates/keyedcache/delete.html:8
+msgid "Cache Delete"
+msgstr "캐쉬 삭제"
+
+#: templates/keyedcache/stats.html:7
+msgid "Cache Stats"
+msgstr "캐쉬 상태"
+
+#: templates/keyedcache/view.html:8
+msgid "Cache View"
+msgstr "캐쉬 보기"
+
diff --git a/keyedcache/locale/pl/LC_MESSAGES/django.mo b/keyedcache/locale/pl/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..14c6acd3
--- /dev/null
+++ b/keyedcache/locale/pl/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/pl/LC_MESSAGES/django.po b/keyedcache/locale/pl/LC_MESSAGES/django.po
new file mode 100644
index 00000000..77974341
--- /dev/null
+++ b/keyedcache/locale/pl/LC_MESSAGES/django.po
@@ -0,0 +1,60 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-09-03 18:10+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: <jerzyk@jerzyk.com>\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"
+
+#: views.py:16
+msgid "Yes"
+msgstr "Tak"
+
+#: views.py:17
+msgid "No"
+msgstr "Nie"
+
+#: views.py:21
+msgid "Key to delete"
+msgstr "Identyfikator do usunięcia"
+
+#: views.py:22
+msgid "Include Children?"
+msgstr "Czy razem z dziećmi?"
+
+#: views.py:23
+msgid "Delete all keys?"
+msgstr "Usunąć wszystkie identyfikatory?"
+
+#: templates/keyedcache/delete.html:6
+#: templates/keyedcache/stats.html:6
+#: templates/keyedcache/view.html:6
+msgid "Home"
+msgstr "Strona startowa"
+
+#: templates/keyedcache/delete.html:7
+#: templates/keyedcache/view.html:7
+msgid "Cache"
+msgstr "Cache"
+
+#: templates/keyedcache/delete.html:8
+msgid "Cache Delete"
+msgstr "Wyczyść pamięć podręczną"
+
+#: templates/keyedcache/stats.html:7
+msgid "Cache Stats"
+msgstr "Statystyka pamięci podręcznej"
+
+#: templates/keyedcache/view.html:8
+msgid "Cache View"
+msgstr "Podgląd pamięci podręcznej"
+
diff --git a/keyedcache/locale/pt_BR/LC_MESSAGES/django.mo b/keyedcache/locale/pt_BR/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..bb1225bd
--- /dev/null
+++ b/keyedcache/locale/pt_BR/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/pt_BR/LC_MESSAGES/django.po b/keyedcache/locale/pt_BR/LC_MESSAGES/django.po
new file mode 100644
index 00000000..4a9a1842
--- /dev/null
+++ b/keyedcache/locale/pt_BR/LC_MESSAGES/django.po
@@ -0,0 +1,61 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the PACKAGE package.
+# Terry Laundos Aguiar <terry@s1solucoes.com.br>, 2008.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-09-05 23:50-0300\n"
+"PO-Revision-Date: 2008-09-05 23:51-0300\n"
+"Last-Translator: Terry Laundos Aguiar <terry@s1solucoes.com.br>\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"
+
+#: views.py:16
+msgid "Yes"
+msgstr "Sim"
+
+#: views.py:17
+msgid "No"
+msgstr "Não"
+
+#: views.py:21
+msgid "Key to delete"
+msgstr "Chave para apagar"
+
+#: views.py:22
+#, fuzzy
+msgid "Include Children?"
+msgstr "Incluir imagens?"
+
+#: views.py:23
+msgid "Delete all keys?"
+msgstr "Apagar todas as chaves?"
+
+#: templates/keyedcache/delete.html:6
+#: templates/keyedcache/stats.html:6
+#: templates/keyedcache/view.html:6
+msgid "Home"
+msgstr "Inicial"
+
+#: templates/keyedcache/delete.html:7
+#: templates/keyedcache/view.html:7
+msgid "Cache"
+msgstr "Cache"
+
+#: templates/keyedcache/delete.html:8
+msgid "Cache Delete"
+msgstr "Apagar cache"
+
+#: templates/keyedcache/stats.html:7
+msgid "Cache Stats"
+msgstr "Estatísticas de cache"
+
+#: templates/keyedcache/view.html:8
+msgid "Cache View"
+msgstr "Exibição de cache"
+
diff --git a/keyedcache/locale/ru/LC_MESSAGES/django.mo b/keyedcache/locale/ru/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..1ff29657
--- /dev/null
+++ b/keyedcache/locale/ru/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/ru/LC_MESSAGES/django.po b/keyedcache/locale/ru/LC_MESSAGES/django.po
new file mode 100644
index 00000000..fbf7be23
--- /dev/null
+++ b/keyedcache/locale/ru/LC_MESSAGES/django.po
@@ -0,0 +1,40 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the Satchmo package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Satchmo\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-12-31 00:49-0600\n"
+"PO-Revision-Date: 2009-03-02 15:50+0300\n"
+"Last-Translator: Данил Семеленов <danil.mail@gmail.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language-Team: \n"
+
+#: templates/keyedcache/delete.html:6
+#: templates/keyedcache/stats.html:6
+#: templates/keyedcache/view.html:6
+msgid "Home"
+msgstr ""
+
+#: templates/keyedcache/delete.html:7
+#: templates/keyedcache/view.html:7
+msgid "Cache"
+msgstr "Кеш"
+
+#: templates/keyedcache/delete.html:8
+msgid "Cache Delete"
+msgstr "Очистка кеша"
+
+#: templates/keyedcache/stats.html:7
+msgid "Cache Stats"
+msgstr "Статистика кеша"
+
+#: templates/keyedcache/view.html:8
+msgid "Cache View"
+msgstr "Просмотр кеша"
+
diff --git a/keyedcache/locale/sv/LC_MESSAGES/django.mo b/keyedcache/locale/sv/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..c9f2fd84
--- /dev/null
+++ b/keyedcache/locale/sv/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/sv/LC_MESSAGES/django.po b/keyedcache/locale/sv/LC_MESSAGES/django.po
new file mode 100644
index 00000000..9aa00572
--- /dev/null
+++ b/keyedcache/locale/sv/LC_MESSAGES/django.po
@@ -0,0 +1,44 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the PACKAGE package.
+# N.L. <kotorinl@yahoo.co.uk>, 2008.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Satchmo svn\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-04-30 23:40+0200\n"
+"PO-Revision-Date: 2008-04-30 23:35+0100\n"
+"Last-Translator: N.L. <kotorinl@yahoo.co.uk>\n"
+"Language-Team: Group\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: Swedish\n"
+"X-Poedit-Basepath: ../../../\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Poedit-Country: SWEDEN\n"
+
+#: templates/keyedcache/delete.html:6
+#: templates/keyedcache/stats.html:6
+#: templates/keyedcache/view.html:6
+msgid "Home"
+msgstr "Hem"
+
+#: templates/keyedcache/delete.html:7
+#: templates/keyedcache/view.html:7
+msgid "Cache"
+msgstr "Cache"
+
+#: templates/keyedcache/delete.html:8
+msgid "Cache Delete"
+msgstr "Radera Cache"
+
+#: templates/keyedcache/stats.html:7
+msgid "Cache Stats"
+msgstr "Cache-statistik"
+
+#: templates/keyedcache/view.html:8
+msgid "Cache View"
+msgstr "Visa Cache"
+
diff --git a/keyedcache/locale/tr/LC_MESSAGES/django.mo b/keyedcache/locale/tr/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..60e5be3f
--- /dev/null
+++ b/keyedcache/locale/tr/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/tr/LC_MESSAGES/django.po b/keyedcache/locale/tr/LC_MESSAGES/django.po
new file mode 100644
index 00000000..4dd6869c
--- /dev/null
+++ b/keyedcache/locale/tr/LC_MESSAGES/django.po
@@ -0,0 +1,42 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the Satchmo package.
+# Selin Çuhadar <selincuhadar@gmail.com>, 2008.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Satchmo\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-12-31 00:49-0600\n"
+"PO-Revision-Date: 2008-06-09 18:18+0200\n"
+"Last-Translator: Selin Çuhadar <selincuhadar@gmail.com>\n"
+"Language-Team: Turkish <selincuhadar@gmail.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Country: TURKEY\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+#: templates/keyedcache/delete.html:6
+#: templates/keyedcache/stats.html:6
+#: templates/keyedcache/view.html:6
+msgid "Home"
+msgstr "Ev"
+
+#: templates/keyedcache/delete.html:7
+#: templates/keyedcache/view.html:7
+msgid "Cache"
+msgstr "Cache"
+
+#: templates/keyedcache/delete.html:8
+msgid "Cache Delete"
+msgstr "Cache'yi Sil"
+
+#: templates/keyedcache/stats.html:7
+msgid "Cache Stats"
+msgstr "Cache İstatistikleri"
+
+#: templates/keyedcache/view.html:8
+msgid "Cache View"
+msgstr "Cache'yi Göster"
+
diff --git a/keyedcache/models.py b/keyedcache/models.py
new file mode 100644
index 00000000..dec68463
--- /dev/null
+++ b/keyedcache/models.py
@@ -0,0 +1,86 @@
+import keyedcache
+import logging
+
+log = logging.getLogger('keyedcache')
+
+class CachedObjectMixin(object):
+ """Provides basic object keyedcache for any objects using this as a mixin."""
+
+ def cache_delete(self, *args, **kwargs):
+ key = self.cache_key(*args, **kwargs)
+ log.debug("clearing cache for %s", key)
+ keyedcache.cache_delete(key, children=True)
+
+ def cache_get(self, *args, **kwargs):
+ key = self.cache_key(*args, **kwargs)
+ return keyedcache.cache_get(key)
+
+ def cache_key(self, *args, **kwargs):
+ keys = [self.__class__.__name__, self]
+ keys.extend(args)
+ return keyedcache.cache_key(keys, **kwargs)
+
+ def cache_reset(self):
+ self.cache_delete()
+ self.cache_set()
+
+ def cache_set(self, *args, **kwargs):
+ val = kwargs.pop('value', self)
+ key = self.cache_key(*args, **kwargs)
+ keyedcache.cache_set(key, value=val)
+
+ def is_cached(self, *args, **kwargs):
+ return keyedcache.is_cached(self.cache_key(*args, **kwargs))
+
+def find_by_id(cls, groupkey, objectid, raises=False):
+ """A helper function to look up an object by id"""
+ ob = None
+ try:
+ ob = keyedcache.cache_get(groupkey, objectid)
+ except keyedcache.NotCachedError, e:
+ try:
+ ob = cls.objects.get(pk=objectid)
+ keyedcache.cache_set(e.key, value=ob)
+
+ except cls.DoesNotExist:
+ log.debug("No such %s: %s", groupkey, objectid)
+ if raises:
+ raise cls.DoesNotExist
+
+ return ob
+
+
+def find_by_key(cls, groupkey, key, raises=False):
+ """A helper function to look up an object by key"""
+ ob = None
+ try:
+ ob = keyedcache.cache_get(groupkey, key)
+ except keyedcache.NotCachedError, e:
+ try:
+ ob = cls.objects.get(key__exact=key)
+ keyedcache.cache_set(e.key, value=ob)
+
+ except cls.DoesNotExist:
+ log.debug("No such %s: %s", groupkey, key)
+ if raises:
+ raise
+
+ return ob
+
+def find_by_slug(cls, groupkey, slug, raises=False):
+ """A helper function to look up an object by slug"""
+ ob = None
+ try:
+ ob = keyedcache.cache_get(groupkey, slug)
+ except keyedcache.NotCachedError, e:
+ try:
+ ob = cls.objects.get(slug__exact=slug)
+ keyedcache.cache_set(e.key, value=ob)
+
+ except cls.DoesNotExist:
+ log.debug("No such %s: %s", groupkey, slug)
+ if raises:
+ raise
+
+ return ob
+
diff --git a/keyedcache/templates/keyedcache/delete.html b/keyedcache/templates/keyedcache/delete.html
new file mode 100644
index 00000000..9449a6d3
--- /dev/null
+++ b/keyedcache/templates/keyedcache/delete.html
@@ -0,0 +1,21 @@
+{% extends "admin/base_site.html" %}
+{% load messaging_tags i18n %}
+
+{% block breadcrumbs %}{% if not is_popup %}
+<div class="breadcrumbs">
+ <a href="/admin/">{% trans "Home" %}</a> &rsaquo;
+ <a href="{% url keyedcache_view %}">{% trans "Cache" %}</a> &rsaquo;
+ {% trans "Cache Delete" %}
+</div>
+{% endif %}{% endblock %}
+
+{% block content %}
+{% show_messages %}
+<p>[<a href="{% url keyedcache_stats %}">Cache Stats</a>] [<a href="{% url keyedcache_view %}">View Cache</a>]</p>
+<h1>Delete From Cache</h1>
+<form method="POST" action="{% url keyedcache_delete %}">
+{{ form.as_p }}
+<input type="submit"/>
+</form>
+
+{% endblock %}
diff --git a/keyedcache/templates/keyedcache/stats.html b/keyedcache/templates/keyedcache/stats.html
new file mode 100644
index 00000000..0a3f17d6
--- /dev/null
+++ b/keyedcache/templates/keyedcache/stats.html
@@ -0,0 +1,21 @@
+{% extends "admin/base_site.html" %}
+{% load messaging_tags i18n %}
+
+{% block breadcrumbs %}{% if not is_popup %}
+<div class="breadcrumbs">
+ <a href="/admin/">{% trans "Home" %}</a> &rsaquo;
+ {% trans "Cache Stats" %}
+</div>
+{% endif %}{% endblock %}
+
+{% block content %}
+{% show_messages %}
+<p>[<a href="{% url keyedcache_view %}">View Cache</a>] [<a href="{% url keyedcache_delete %}">Delete from Cache</a>]
+<h1>Cache Stats</h1>
+<p>Backend: {{ cache_backend }} ({% if cache_running %}running{% else %}down{% endif %})</p>
+<p>Timeout: {{ cache_time }}</p>
+<p>Keys in cache: {{ cache_count }}</p>
+<p>Cache Calls: {{ cache_calls }}</p>
+<p>Cache Hits: {{ cache_hits }}</p>
+<p>Cache Hit Rate: {{ hit_rate }}%</p>
+{% endblock %}
diff --git a/keyedcache/templates/keyedcache/view.html b/keyedcache/templates/keyedcache/view.html
new file mode 100644
index 00000000..e28c5a0e
--- /dev/null
+++ b/keyedcache/templates/keyedcache/view.html
@@ -0,0 +1,18 @@
+{% extends "admin/base_site.html" %}
+{% load messaging_tags i18n %}
+
+{% block breadcrumbs %}{% if not is_popup %}
+<div class="breadcrumbs">
+ <a href="/admin/">{% trans "Home" %}</a> &rsaquo;
+ <a href="{% url keyedcache_view %}">{% trans "Cache" %}</a> &rsaquo;
+ {% trans "Cache View" %}
+</div>
+{% endif %}{% endblock %}
+
+{% block content %}
+{% show_messages %}
+<p>[<a href="{% url keyedcache_stats %}">Cache Stats</a>] [<a href="{% url keyedcache_delete %}">Delete from Cache</a>]
+<h1>Cache Keys</h1>
+<p style="font-size:82%;">{% for key in cached_keys %}{{ key }}, {% endfor %}
+</p>
+{% endblock %}
diff --git a/keyedcache/tests.py b/keyedcache/tests.py
new file mode 100644
index 00000000..8abb8dd3
--- /dev/null
+++ b/keyedcache/tests.py
@@ -0,0 +1,150 @@
+import keyedcache
+import random
+from django.test import TestCase
+import time
+
+CACHE_HIT=0
+
+def cachetest(a,b,c):
+ global CACHE_HIT
+ CACHE_HIT += 1
+ r = [random.randrange(0,1000) for x in range(0,3)]
+ ret = [r, a + r[0], b + r[1], c + r[2]]
+ return ret
+
+cachetest = keyedcache.cache_function(2)(cachetest)
+
+class DecoratorTest(TestCase):
+
+ def testCachePut(self):
+ d = cachetest(1,2,3)
+ self.assertEqual(CACHE_HIT,1)
+
+ d2 = cachetest(1,2,3)
+ self.assertEqual(CACHE_HIT,1)
+ self.assertEqual(d, d2)
+
+ seeds = d[0]
+ self.assertEqual(seeds[0] + 1, d[1])
+ self.assertEqual(seeds[1] + 2, d[2])
+ self.assertEqual(seeds[2] + 3, d[3])
+
+ time.sleep(3)
+ d3 = cachetest(1,2,3)
+ self.assertEqual(CACHE_HIT,2)
+ self.assertNotEqual(d, d3)
+
+ def testDeleteCachedFunction(self):
+ orig = cachetest(10,20,30)
+ keyedcache.cache_delete_function(cachetest)
+ after = cachetest(10,20,30)
+ self.assertNotEqual(orig,keyedcache)
+
+class CachingTest(TestCase):
+
+ def testCacheGetFail(self):
+ try:
+ keyedcache.cache_get('x')
+ self.fail('should have raised NotCachedError')
+ except keyedcache.NotCachedError:
+ pass
+
+ def testCacheGetOK(self):
+ one = [1,2,3,4]
+ keyedcache.cache_set('ok', value=one, length=2)
+ two = keyedcache.cache_get('ok')
+ self.assertEqual(one, two)
+
+ time.sleep(5)
+ try:
+ three = keyedcache.cache_get('ok')
+ self.fail('should have raised NotCachedError, got %s' % three)
+ except keyedcache.NotCachedError:
+ pass
+
+ def testCacheGetDefault(self):
+ chk = keyedcache.cache_get('default',default='-')
+ self.assertEqual(chk, '-')
+
+
+ def testDelete(self):
+ keyedcache.cache_set('del', value=True)
+
+ for x in range(0,10):
+ keyedcache.cache_set('del', 'x', x, value=True)
+ for y in range(0,5):
+ keyedcache.cache_set('del', 'x', x, 'y', y, value=True)
+
+ # check to make sure all the values are in the cache
+ self.assert_(keyedcache.cache_get('del', default=False))
+ for x in range(0,10):
+ self.assert_(keyedcache.cache_get('del', 'x', x, default=False))
+ for y in range(0,5):
+ self.assert_(keyedcache.cache_get('del', 'x', x, 'y', y, default=False))
+
+ # try to delete just one
+ killed = keyedcache.cache_delete('del','x',1)
+ self.assertEqual([keyedcache.CACHE_PREFIX + "::del::x::1"], killed)
+ self.assertFalse(keyedcache.cache_get('del', 'x', 1, default=False))
+
+ # but the others are still there
+ self.assert_(keyedcache.cache_get('del', 'x', 2, default=False))
+
+ # now kill all of del::x::1
+ killed = keyedcache.cache_delete('del','x', 1, children=True)
+ for y in range(0,5):
+ self.assertFalse(keyedcache.cache_get('del', 'x', 1, 'y', y, default=False))
+
+ # but del::x::2 and children are there
+ self.assert_(keyedcache.cache_get('del','x',2,'y',1, default=False))
+
+ # kill the rest
+ killed = keyedcache.cache_delete('del', children=True)
+ self.assertFalse(keyedcache.cache_get('del',default=False))
+ for x in range(0,10):
+ self.assertFalse(keyedcache.cache_get('del', 'x', x, default=False))
+ for y in range(0,5):
+ self.assertFalse(keyedcache.cache_get('del', 'x', x, 'y', y, default=False))
+
+
+class TestCacheDisable(TestCase):
+
+ def testDisable(self):
+ keyedcache.cache_set('disabled', value=False)
+ v = keyedcache.cache_get('disabled')
+ self.assertEqual(v, False)
+
+ keyedcache.cache_enable(False)
+ keyedcache.cache_set('disabled', value=True)
+ try:
+ keyedcache.cache_get('disabled')
+ self.fail('should have raised NotCachedError')
+ except keyedcache.NotCachedError, nce:
+ key = keyedcache.cache_key('disabled')
+ self.assertEqual(nce.key, key)
+
+ keyedcache.cache_enable()
+ v2 = keyedcache.cache_get('disabled')
+ # should still be False, since the cache was disabled
+ self.assertEqual(v2, False)
+
+class TestKeyMaker(TestCase):
+
+ def testSimpleKey(self):
+ v = keyedcache.cache_key('test')
+ self.assertEqual(v, keyedcache.CACHE_PREFIX + '::test')
+
+ def testDualKey(self):
+ v = keyedcache.cache_key('test', 2)
+ self.assertEqual(v, keyedcache.CACHE_PREFIX + '::test::2')
+
+ def testPairedKey(self):
+ v = keyedcache.cache_key('test', more='yes')
+ self.assertEqual(v, keyedcache.CACHE_PREFIX + '::test::more::yes')
+
+ def testPairedDualKey(self):
+ v = keyedcache.cache_key('test', 3, more='yes')
+ self.assertEqual(v, keyedcache.CACHE_PREFIX + '::test::3::more::yes')
+
+
+
diff --git a/keyedcache/threaded.py b/keyedcache/threaded.py
new file mode 100644
index 00000000..997fddbc
--- /dev/null
+++ b/keyedcache/threaded.py
@@ -0,0 +1,32 @@
+"""Causes the keyedcache to also use a first-level cache in memory - this can cut 30-40% of memcached calls.
+
+To enable, add this to some models.py file in an app::
+
+ from keyedcache import threaded
+ threaded.start_listening()
+
+"""
+from threaded_multihost import threadlocals
+from django.core.signals import request_started, request_finished
+from keyedcache import cache_clear_request, cache_use_request_caching
+import random
+import logging
+log = logging.getLogger('keyedcache.threaded')
+
+def set_request_uid(sender, *args, **kwargs):
+ """Puts a unique id into the thread"""
+ tid = random.randrange(1,10000000)
+ threadlocals.set_thread_variable('request_uid', tid)
+ #log.debug('request UID: %s', tid)
+
+def clear_request_uid(sender, *args, **kwargs):
+ """Removes the thread cache for this request"""
+ tid = threadlocals.get_thread_variable('request_uid', -1)
+ if tid > -1:
+ cache_clear_request(tid)
+
+def start_listening():
+ log.debug('setting up threaded keyedcache')
+ cache_use_request_caching()
+ request_started.connect(set_request_uid)
+ request_finished.connect(clear_request_uid)
diff --git a/keyedcache/urls.py b/keyedcache/urls.py
new file mode 100644
index 00000000..1a944043
--- /dev/null
+++ b/keyedcache/urls.py
@@ -0,0 +1,10 @@
+"""
+URLConf for Caching app
+"""
+
+from django.conf.urls.defaults import patterns
+urlpatterns = patterns('keyedcache.views',
+ (r'^$', 'stats_page', {}, 'keyedcache_stats'),
+ (r'^view/$', 'view_page', {}, 'keyedcache_view'),
+ (r'^delete/$', 'delete_page', {}, 'keyedcache_delete'),
+)
diff --git a/keyedcache/utils.py b/keyedcache/utils.py
new file mode 100644
index 00000000..29b8fd71
--- /dev/null
+++ b/keyedcache/utils.py
@@ -0,0 +1,14 @@
+import types
+
+def is_string_like(maybe):
+ """Test value to see if it acts like a string"""
+ try:
+ maybe+""
+ except TypeError:
+ return 0
+ else:
+ return 1
+
+
+def is_list_or_tuple(maybe):
+ return isinstance(maybe, (types.TupleType, types.ListType))
diff --git a/keyedcache/views.py b/keyedcache/views.py
new file mode 100644
index 00000000..9a3c1219
--- /dev/null
+++ b/keyedcache/views.py
@@ -0,0 +1,103 @@
+from django import forms
+from django.conf import settings
+from django.contrib.auth.decorators import user_passes_test
+from django.http import HttpResponseRedirect
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.utils.translation import ugettext_lazy as _
+import keyedcache
+import logging
+
+log = logging.getLogger('keyedcache.views')
+
+YN = (
+ ('Y', _('Yes')),
+ ('N', _('No')),
+ )
+
+class CacheDeleteForm(forms.Form):
+ tag = forms.CharField(label=_('Key to delete'), required=False)
+ children = forms.ChoiceField(label=_('Include Children?'), choices=YN, initial="Y")
+ kill_all = forms.ChoiceField(label=_('Delete all keys?'), choices=YN, initial="Y")
+
+ def delete_cache(self):
+
+ data = self.cleaned_data
+ if data['kill_all'] == "Y":
+ keyedcache.cache_delete()
+ result = "Deleted all keys"
+ elif data['tag']:
+ keyedcache.cache_delete(data['tag'], children=data['children'])
+ if data['children'] == "Y":
+ result = "Deleted %s and children" % data['tag']
+ else:
+ result = "Deleted %s" % data['tag']
+ else:
+ result = "Nothing selected to delete"
+
+ log.debug(result)
+ return result
+
+def stats_page(request):
+ calls = keyedcache.CACHE_CALLS
+ hits = keyedcache.CACHE_HITS
+
+ if (calls and hits):
+ rate = float(keyedcache.CACHE_HITS)/keyedcache.CACHE_CALLS*100
+ else:
+ rate = 0
+
+ try:
+ running = keyedcache.cache_require()
+
+ except keyedcache.CacheNotRespondingError:
+ running = False
+
+ ctx = RequestContext(request, {
+ 'cache_count' : len(keyedcache.CACHED_KEYS),
+ 'cache_running' : running,
+ 'cache_time' : settings.CACHE_TIMEOUT,
+ 'cache_backend' : settings.CACHE_BACKEND,
+ 'cache_calls' : keyedcache.CACHE_CALLS,
+ 'cache_hits' : keyedcache.CACHE_HITS,
+ 'hit_rate' : "%02.1f" % rate
+ })
+
+ return render_to_response('keyedcache/stats.html', context_instance=ctx)
+
+stats_page = user_passes_test(lambda u: u.is_authenticated() and u.is_staff, login_url='/accounts/login/')(stats_page)
+
+def view_page(request):
+ keys = keyedcache.CACHED_KEYS.keys()
+
+ keys.sort()
+
+ ctx = RequestContext(request, {
+ 'cached_keys' : keys,
+ })
+
+ return render_to_response('keyedcache/view.html', context_instance=ctx)
+
+view_page = user_passes_test(lambda u: u.is_authenticated() and u.is_staff, login_url='/accounts/login/')(view_page)
+
+def delete_page(request):
+ log.debug("delete_page")
+ if request.method == "POST":
+ form = CacheDeleteForm(request.POST)
+ if form.is_valid():
+ log.debug('delete form valid')
+ results = form.delete_cache()
+ return HttpResponseRedirect('../')
+ else:
+ log.debug("Errors in form: %s", form.errors)
+ else:
+ log.debug("new form")
+ form = CacheDeleteForm()
+
+ ctx = RequestContext(request, {
+ 'form' : form,
+ })
+
+ return render_to_response('keyedcache/delete.html', context_instance=ctx)
+
+delete_page = user_passes_test(lambda u: u.is_authenticated() and u.is_staff, login_url='/accounts/login/')(delete_page)
diff --git a/livesettings/__init__.py b/livesettings/__init__.py
new file mode 100644
index 00000000..49aaacc9
--- /dev/null
+++ b/livesettings/__init__.py
@@ -0,0 +1,16 @@
+"""Database persistent administrative settings with defaults.
+
+This code is a large fork of the excellent "dbsettings" code found at
+http://code.google.com/p/django-values/
+
+The items set here are intended to be changeable during runtime, and do not require a
+programmer to test or install.
+
+Appropriate: Your google code for adwords.
+Inappropriate: The keyedcache timeout for the store.
+
+"""
+
+from functions import *
+from models import *
+from values import * \ No newline at end of file
diff --git a/livesettings/forms.py b/livesettings/forms.py
new file mode 100644
index 00000000..b1c5f6f4
--- /dev/null
+++ b/livesettings/forms.py
@@ -0,0 +1,38 @@
+from django import forms
+from livesettings import *
+import logging
+
+log = logging.getLogger('configuration')
+
+class SettingsEditor(forms.Form):
+ "Base editor, from which customized forms are created"
+
+ def __init__(self, *args, **kwargs):
+ settings = kwargs.pop('settings')
+ super(SettingsEditor, self).__init__(*args, **kwargs)
+ flattened = []
+ groups = []
+ for setting in settings:
+ if isinstance(setting, ConfigurationGroup):
+ for s in setting:
+ flattened.append(s)
+ else:
+ flattened.append(setting)
+
+ for setting in flattened:
+ # Add the field to the customized field list
+ kw = {
+ 'label': setting.description,
+ 'help_text': setting.help_text,
+ # Provide current setting values for initializing the form
+ 'initial': setting.editor_value
+ }
+ field = setting.make_field(**kw)
+
+ k = '%s__%s' % (setting.group.key, setting.key)
+ self.fields[k] = field
+ if not setting.group in groups:
+ groups.append(setting.group)
+ #log.debug("Added field: %s = %s" % (k, str(field)))
+
+ self.groups = groups \ No newline at end of file
diff --git a/livesettings/functions.py b/livesettings/functions.py
new file mode 100644
index 00000000..8b919083
--- /dev/null
+++ b/livesettings/functions.py
@@ -0,0 +1,247 @@
+from django.utils.translation import ugettext
+from livesettings import values
+from livesettings.models import SettingNotSet
+from livesettings.utils import is_string_like
+
+import logging
+
+log = logging.getLogger('configuration')
+
+_NOTSET = object()
+
+class ConfigurationSettings(object):
+ """A singleton manager for ConfigurationSettings"""
+
+ class __impl(object):
+ def __init__(self):
+ self.settings = values.SortedDotDict()
+ self.prereg = {}
+
+ def __getitem__(self, key):
+ """Get an element either by ConfigurationGroup object or by its key"""
+ key = self._resolve_key(key)
+ return self.settings.get(key)
+
+ def __getattr__(self, key):
+ """Get an element either by ConfigurationGroup object or by its key"""
+ try:
+ return self[key]
+ except:
+ raise AttributeError, key
+
+ def __iter__(self):
+ for v in self.groups():
+ yield v
+
+ def __len__(self):
+ return len(self.settings)
+
+ def __contains__(self, key):
+ try:
+ key = self._resolve_key(key)
+ return self.settings.has_key(key)
+ except:
+ return False
+
+ def _resolve_key(self, raw):
+ if is_string_like(raw):
+ key = raw
+
+ elif isinstance(raw, values.ConfigurationGroup):
+ key = raw.key
+
+ else:
+ group = self.groups()[raw]
+ key = group.key
+
+ return key
+
+ def get_config(self, group, key):
+ try:
+ if isinstance(group, values.ConfigurationGroup):
+ group = group.key
+
+ cg = self.settings.get(group, None)
+ if not cg:
+ raise SettingNotSet('%s config group does not exist' % group)
+
+ else:
+ return cg[key]
+ except KeyError:
+ raise SettingNotSet('%s.%s' % (group, key))
+
+ def groups(self):
+ """Return ordered list"""
+ return self.settings.values()
+
+ def has_config(self, group, key):
+ if isinstance(group, values.ConfigurationGroup):
+ group = group.key
+
+ cfg = self.settings.get(group, None)
+ if cfg and key in cfg:
+ return True
+ else:
+ return False
+
+ def preregister_choice(self, group, key, choice):
+ """Setup a choice for a group/key which hasn't been instantiated yet."""
+ k = (group, key)
+ if self.prereg.has_key(k):
+ self.prereg[k].append(choice)
+ else:
+ self.prereg[k] = [choice]
+
+ def register(self, value):
+ g = value.group
+ if not isinstance(g, values.ConfigurationGroup):
+ raise ValueError('value.group should be an instance of ConfigurationGroup')
+
+ groupkey = g.key
+ valuekey = value.key
+
+ k = (groupkey, valuekey)
+ if self.prereg.has_key(k):
+ for choice in self.prereg[k]:
+ value.add_choice(choice)
+
+ if not groupkey in self.settings:
+ self.settings[groupkey] = g
+
+ self.settings[groupkey][valuekey] = value
+
+ return value
+
+ __instance = None
+
+ def __init__(self):
+ if ConfigurationSettings.__instance is None:
+ ConfigurationSettings.__instance = ConfigurationSettings.__impl()
+ #ConfigurationSettings.__instance.load_app_configurations()
+
+ self.__dict__['_ConfigurationSettings__instance'] = ConfigurationSettings.__instance
+
+ def __getattr__(self, attr):
+ """ Delegate access to implementation """
+ return getattr(self.__instance, attr)
+
+ def __getitem__(self, key):
+ return self.__instance[key]
+
+ def __len__(self):
+ return len(self.__instance)
+
+ def __setattr__(self, attr, value):
+ """ Delegate access to implementation """
+ return setattr(self.__instance, attr, value)
+
+ def __unicode__(self):
+ return u"ConfigurationSettings: " + unicode(self.groups())
+
+def config_exists(group, key):
+ """Test to see if a setting has been registered"""
+
+ return ConfigurationSettings().has_config(group, key)
+
+def config_get(group, key):
+ """Get a configuration setting"""
+ try:
+ return ConfigurationSettings().get_config(group, key)
+ except SettingNotSet:
+ log.debug('SettingNotSet: %s.%s', group, key)
+ raise
+
+def config_get_group(group):
+ return ConfigurationSettings()[group]
+
+def config_collect_values(group, groupkey, key, unique=True, skip_missing=True):
+ """Look up (group, groupkey) from config, then take the values returned and
+ use them as groups for a second-stage lookup.
+
+ For example:
+
+ config_collect_values(PAYMENT, MODULES, CREDITCHOICES)
+
+ Stage 1: ['PAYMENT_GOOGLE', 'PAYMENT_AUTHORIZENET']
+ Stage 2: config_value('PAYMENT_GOOGLE', 'CREDITCHOICES')
+ + config_value('PAYMENT_AUTHORIZENET', 'CREDITCHOICES')
+ Stage 3: (if unique is true) remove dupes
+ """
+ groups = config_value(group, groupkey)
+
+ ret = []
+ for g in groups:
+ try:
+ ret.append(config_value(g, key))
+ except KeyError, ke:
+ if not skip_missing:
+ raise SettingNotSet('No config %s.%s' % (g, key))
+
+ if unique:
+ out = []
+ for x in ret:
+ if not x in out:
+ out.append(x)
+ ret = out
+
+ return ret
+
+def config_register(value):
+ """Register a value or values.
+
+ Parameters:
+ -A Value
+ """
+ return ConfigurationSettings().register(value)
+
+def config_register_list(*args):
+ for value in args:
+ config_register(value)
+
+def config_value(group, key, default=_NOTSET):
+ """Get a value from the configuration system"""
+ try:
+ return config_get(group, key).value
+ except SettingNotSet:
+ if default != _NOTSET:
+ return default
+ raise
+
+def config_value_safe(group, key, default_value):
+ """Get a config value with a default fallback, safe for use during SyncDB."""
+ raw = default_value
+
+ try:
+ raw = config_value(group, key)
+ except SettingNotSet:
+ pass
+ except ImportError, e:
+ log.warn("Error getting %s.%s, OK if you are in SyncDB.", group, key)
+
+ return raw
+
+
+def config_choice_values(group, key, skip_missing=True, translate=False):
+ """Get pairs of key, label from the setting."""
+ try:
+ cfg = config_get(group, key)
+ choices = cfg.choice_values
+
+ except SettingNotSet:
+ if skip_missing:
+ return []
+ else:
+ raise SettingNotSet('%s.%s' % (group, key))
+
+ if translate:
+ choices = [(k, ugettext(v)) for k, v in choices]
+
+ return choices
+
+def config_add_choice(group, key, choice):
+ """Add a choice to a value"""
+ if config_exists(group, key):
+ cfg = config_get(group, key)
+ cfg.add_choice(choice)
+ else:
+ ConfigurationSettings().preregister_choice(group, key, choice)
diff --git a/livesettings/locale/de/LC_MESSAGES/django.mo b/livesettings/locale/de/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..e176bc53
--- /dev/null
+++ b/livesettings/locale/de/LC_MESSAGES/django.mo
Binary files differ
diff --git a/livesettings/locale/de/LC_MESSAGES/django.po b/livesettings/locale/de/LC_MESSAGES/django.po
new file mode 100644
index 00000000..1cef701b
--- /dev/null
+++ b/livesettings/locale/de/LC_MESSAGES/django.po
@@ -0,0 +1,101 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the Satchmo package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-03-22 15:10+0100\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"
+
+#: values.py:88
+msgid "Base Settings"
+msgstr "Basiseinstellungen"
+
+#: values.py:194
+msgid "Default value: \"\""
+msgstr "Standardwert: \"\""
+
+#: values.py:201
+msgid "Default value: "
+msgstr "Standardwert: "
+
+#: values.py:204
+#, python-format
+msgid "Default value: %s"
+msgstr "Standardwert: %s"
+
+#: templates/livesettings/group_settings.html:10
+#: templates/livesettings/site_settings.html:10
+msgid "Home"
+msgstr "Start"
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:7
+msgid "Log out"
+msgstr "Abmelden"
+
+#: templates/livesettings/group_settings.html:18
+#: templates/livesettings/site_settings.html:18
+#, fuzzy
+msgid "Please correct the error below."
+msgid_plural "Please correct the errors below."
+msgstr[0] ""
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+msgstr[1] ""
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:7
+#, fuzzy
+msgid "Documentation"
+msgstr ""
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:7
+msgid "Change password"
+msgstr "Passwort ändern"
+
+#: templates/livesettings/site_settings.html:11
+msgid "Edit Site Settings"
+msgstr ""
+
+#: templates/livesettings/group_settings.html:11
+msgid "Edit Group Settings"
+msgstr ""
+
+#: templates/livesettings/group_settings.html:24
+#, python-format
+msgid "Settings included in %(name)s."
+msgstr ""
+
+#: templates/livesettings/group_settings.html:49
+#: templates/livesettings/site_settings.html:61
+msgid "You don't have permission to edit values."
+msgstr ""
+
+#: templates/livesettings/site_settings.html:34
+#, python-format
+msgid "Group settings: %(name)s"
+msgstr ""
+
diff --git a/livesettings/locale/en/LC_MESSAGES/django.mo b/livesettings/locale/en/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..c2bc0b94
--- /dev/null
+++ b/livesettings/locale/en/LC_MESSAGES/django.mo
Binary files differ
diff --git a/livesettings/locale/en/LC_MESSAGES/django.po b/livesettings/locale/en/LC_MESSAGES/django.po
new file mode 100644
index 00000000..45eb23a5
--- /dev/null
+++ b/livesettings/locale/en/LC_MESSAGES/django.po
@@ -0,0 +1,100 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the Satchmo package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-12-31 00:49-0600\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"
+
+#: values.py:88
+msgid "Base Settings"
+msgstr ""
+
+#: values.py:194
+msgid "Default value: \"\""
+msgstr ""
+
+#: values.py:201
+msgid "Default value: "
+msgstr ""
+
+#: values.py:204
+#, python-format
+msgid "Default value: %s"
+msgstr ""
+
+#: templates/livesettings/group_settings.html:10
+#: templates/livesettings/site_settings.html:10
+msgid "Home"
+msgstr ""
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:7
+msgid "Log out"
+msgstr ""
+
+#: templates/livesettings/group_settings.html:18
+#: templates/livesettings/site_settings.html:18
+#, fuzzy
+msgid "Please correct the error below."
+msgid_plural "Please correct the errors below."
+msgstr[0] ""
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+msgstr[1] ""
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:7
+msgid "Documentation"
+msgstr ""
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:7
+msgid "Change password"
+msgstr ""
+
+#: templates/livesettings/group_settings.html:11
+msgid "Edit Group Settings"
+msgstr ""
+
+#: templates/livesettings/group_settings.html:24
+#, python-format
+msgid "Settings included in %(name)s."
+msgstr ""
+
+#: templates/livesettings/group_settings.html:49
+#: templates/livesettings/site_settings.html:61
+msgid "You don't have permission to edit values."
+msgstr ""
+
+#: templates/livesettings/site_settings.html:11
+msgid "Edit Site Settings"
+msgstr ""
+
+#: templates/livesettings/site_settings.html:34
+#, python-format
+msgid "Group settings: %(name)s"
+msgstr ""
+
diff --git a/livesettings/locale/es/LC_MESSAGES/django.po b/livesettings/locale/es/LC_MESSAGES/django.po
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/livesettings/locale/es/LC_MESSAGES/django.po
diff --git a/livesettings/locale/fr/LC_MESSAGES/django.mo b/livesettings/locale/fr/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..dd872edd
--- /dev/null
+++ b/livesettings/locale/fr/LC_MESSAGES/django.mo
Binary files differ
diff --git a/livesettings/locale/fr/LC_MESSAGES/django.po b/livesettings/locale/fr/LC_MESSAGES/django.po
new file mode 100644
index 00000000..90475585
--- /dev/null
+++ b/livesettings/locale/fr/LC_MESSAGES/django.po
@@ -0,0 +1,113 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# Jacques Moulin <jacques@tpi.be>, 2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-11-02 16:11+0100\n"
+"PO-Revision-Date: 2008-11-02 17:51+0100\n"
+"Last-Translator: Jacques Moulin <jacques@tpi.be>\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"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Poedit-Language: French\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+#: templates/livesettings/group_settings.html.py:10
+#: templates/livesettings/site_settings.html.py:25
+#: templates/livesettings/group_settings.html.py:10
+#: templates/livesettings/site_settings.html.py:25
+msgid "Home"
+msgstr "Accueil"
+
+#: models.py:76
+#: models.py:115
+msgid "Site"
+msgstr "Site"
+
+#: values.py:94
+msgid "Base Settings"
+msgstr "Configuration de base"
+
+#: values.py:200
+msgid "Default value: \"\""
+msgstr "Valeur par défaut: \"\""
+
+#: values.py:207
+msgid "Default value: "
+msgstr "Valeur par défaut:"
+
+#: values.py:210
+#, python-format
+msgid "Default value: %s"
+msgstr "Valeur par défaut: %s"
+
+#: templates/livesettings/group_settings.html.py:7
+#: templates/livesettings/site_settings.html.py:22
+#: templates/livesettings/group_settings.html.py:7
+#: templates/livesettings/site_settings.html.py:22
+msgid "Documentation"
+msgstr "Documentation"
+
+#: templates/livesettings/group_settings.html.py:7
+#: templates/livesettings/site_settings.html.py:22
+#: templates/livesettings/group_settings.html.py:7
+#: templates/livesettings/site_settings.html.py:22
+msgid "Change password"
+msgstr "Modifier le mot de passe"
+
+#: templates/livesettings/group_settings.html.py:7
+#: templates/livesettings/site_settings.html.py:22
+#: templates/livesettings/group_settings.html.py:7
+#: templates/livesettings/site_settings.html.py:22
+msgid "Log out"
+msgstr "Se déconnecter"
+
+#: templates/livesettings/group_settings.html.py:11
+#: templates/livesettings/group_settings.html.py:11
+msgid "Edit Group Settings"
+msgstr "Editer les paramètres de groupe"
+
+#: templates/livesettings/group_settings.html.py:18
+#: templates/livesettings/site_settings.html.py:43
+#: templates/livesettings/group_settings.html.py:18
+#: templates/livesettings/site_settings.html.py:41
+msgid "Please correct the error below."
+msgid_plural "Please correct the errors below."
+msgstr[0] "Veuillez corriger l'erreur ci-dessous:"
+msgstr[1] "Veuillez corriger les erreurs ci-dessous:"
+
+#: templates/livesettings/group_settings.html.py:24
+#: templates/livesettings/group_settings.html.py:24
+msgid "Settings included in %(name)s."
+msgstr "Paramètres inclus dans %(name)s."
+
+#: templates/livesettings/group_settings.html.py:49
+#: templates/livesettings/site_settings.html.py:89
+#: templates/livesettings/group_settings.html.py:49
+#: templates/livesettings/site_settings.html.py:87
+msgid "You don't have permission to edit values."
+msgstr "Vous n'avez pas le droit d'éditer les valeurs."
+
+#: templates/livesettings/site_settings.html.py:26
+#: templates/livesettings/site_settings.html.py:26
+msgid "Edit Site Settings"
+msgstr "Editer les paramètres du site"
+
+#: templates/livesettings/site_settings.html.py:59
+#: templates/livesettings/site_settings.html.py:58
+msgid "Group settings: %(name)s"
+msgstr "Paramètres du groupe: %(name)s"
+
+#: templates/livesettings/site_settings.html.py:86
+#: templates/livesettings/site_settings.html.py:84
+msgid "Uncollapse all"
+msgstr "Déployer tout"
+
+#: templates/livesettings/_admin_site_views.html.py:5
+msgid "Sites"
+msgstr "Sites"
+
diff --git a/livesettings/locale/he/LC_MESSAGES/django.mo b/livesettings/locale/he/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..04270a04
--- /dev/null
+++ b/livesettings/locale/he/LC_MESSAGES/django.mo
Binary files differ
diff --git a/livesettings/locale/he/LC_MESSAGES/django.po b/livesettings/locale/he/LC_MESSAGES/django.po
new file mode 100644
index 00000000..362f5612
--- /dev/null
+++ b/livesettings/locale/he/LC_MESSAGES/django.po
@@ -0,0 +1,98 @@
+# translation of Satchmo
+# Copyright (C) 2008 The Satchmo Project
+# This file is distributed under the same license as the Satchmo package.
+#
+# Aviv Greenberg <avivgr@gmail.com>, 2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: django\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-03-13 23:02+0200\n"
+"PO-Revision-Date: 2009-03-22 07:45\n"
+"Last-Translator: Aviv Greenberg <avivgr@gmail.com>\n"
+"Language-Team: <en@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: KBabel 1.11.4\n"
+"X-Translated-Using: django-rosetta 0.4.0\n"
+
+#: models.py:75 models.py:114
+msgid "Site"
+msgstr "אתר"
+
+#: values.py:96
+msgid "Base Settings"
+msgstr "תצורה בסיסית"
+
+#: values.py:202
+msgid "Default value: \"\""
+msgstr "ברירת מחדל:\"\""
+
+#: values.py:209
+msgid "Default value: "
+msgstr "ברירת מחדל:"
+
+#: values.py:212
+#, python-format
+msgid "Default value: %s"
+msgstr "ברירת מחדל:%s"
+
+#: templates/livesettings/_admin_site_views.html:4
+msgid "Sites"
+msgstr "אתרים"
+
+#: templates/livesettings/group_settings.html:11
+#: templates/livesettings/site_settings.html:23
+msgid "Documentation"
+msgstr "תיעוד"
+
+#: templates/livesettings/group_settings.html:11
+#: templates/livesettings/site_settings.html:23
+msgid "Change password"
+msgstr "שינוי סיסמה"
+
+#: templates/livesettings/group_settings.html:11
+#: templates/livesettings/site_settings.html:23
+msgid "Log out"
+msgstr "יציאה"
+
+#: templates/livesettings/group_settings.html:14
+#: templates/livesettings/site_settings.html:26
+msgid "Home"
+msgstr "דף הבית"
+
+#: templates/livesettings/group_settings.html:15
+msgid "Edit Group Settings"
+msgstr "ערוך הגדרות קבוצה"
+
+#: templates/livesettings/group_settings.html:22
+#: templates/livesettings/site_settings.html:44
+msgid "Please correct the error below."
+msgid_plural "Please correct the errors below."
+msgstr[0] "נא לתקן את השגיאה המופיעה מתחת."
+msgstr[1] "נא לתקן את השגיאות המופיעות מתחת."
+
+#: templates/livesettings/group_settings.html:28
+#, python-format
+msgid "Settings included in %(name)s."
+msgstr "הגדרות כלולות %(name)s"
+
+#: templates/livesettings/group_settings.html:53
+#: templates/livesettings/site_settings.html:90
+msgid "You don't have permission to edit values."
+msgstr "אינך מורשה לערוך ערכים."
+
+#: templates/livesettings/site_settings.html:27
+msgid "Edit Site Settings"
+msgstr "ערוך הגדרות אתר"
+
+#: templates/livesettings/site_settings.html:60
+#, python-format
+msgid "Group settings: %(name)s"
+msgstr "הגדרות קבוצה: %(name)s"
+
+#: templates/livesettings/site_settings.html:87
+msgid "Uncollapse all"
+msgstr "הסתר פרטים"
diff --git a/livesettings/locale/it/LC_MESSAGES/django.mo b/livesettings/locale/it/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..05c50952
--- /dev/null
+++ b/livesettings/locale/it/LC_MESSAGES/django.mo
Binary files differ
diff --git a/livesettings/locale/it/LC_MESSAGES/django.po b/livesettings/locale/it/LC_MESSAGES/django.po
new file mode 100644
index 00000000..66401866
--- /dev/null
+++ b/livesettings/locale/it/LC_MESSAGES/django.po
@@ -0,0 +1,106 @@
+# translation of django.po to Italiano
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the PACKAGE package.
+#
+# costantino giuliodori <costantino.giuliodori@gmail.com>, 2007.
+# Alessandro Ronchi <alessandro.ronchi@soasi.com>, 2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: django\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-09-27 09:16-0700\n"
+"PO-Revision-Date: 2008-09-30 13:13+0200\n"
+"Last-Translator: Alessandro Ronchi <alessandro.ronchi@soasi.com>\n"
+"Language-Team: Italiano <it@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: KBabel 1.11.4\n"
+"Plural-Forms: nplurals=2; plural=n > 1\n"
+
+#: templates/livesettings/group_settings.html:10
+#: templates/livesettings/site_settings.html:25
+msgid "Home"
+msgstr "Pagina iniziale"
+
+#: models.py:76
+#: models.py:115
+msgid "Site"
+msgstr "Sito"
+
+#: values.py:94
+msgid "Base Settings"
+msgstr "Impostazioni base"
+
+#: values.py:200
+msgid "Default value: \"\""
+msgstr "Valore di default: \"\""
+
+#: values.py:207
+msgid "Default value: "
+msgstr "Valore di default: "
+
+#: values.py:210
+#, python-format
+msgid "Default value: %s"
+msgstr "Valore di default:%s"
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:22
+# translated = "Extra di spedizione"
+msgid "Documentation"
+msgstr "Documentazione"
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:22
+msgid "Change password"
+msgstr "Cambia Password"
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:22
+msgid "Log out"
+msgstr "Esci"
+
+#: templates/livesettings/group_settings.html:11
+msgid "Edit Group Settings"
+msgstr "Modifica le impostazioni del Gruppo"
+
+#: templates/livesettings/group_settings.html:18
+#: templates/livesettings/site_settings.html:43
+# translated = ""
+msgid "Please correct the error below."
+msgid_plural "Please correct the errors below."
+msgstr[0] "Correggi l'errore indicato di seguito."
+msgstr[1] "Correggi gli errori indicati di seguito."
+
+#: templates/livesettings/group_settings.html:24
+# translated = "Modificare le impostazioni di gruppo"
+#, python-format
+msgid "Settings included in %(name)s."
+msgstr "Impostazioni incluse in %(name)s."
+
+#: templates/livesettings/group_settings.html:49
+#: templates/livesettings/site_settings.html:89
+# translated = "Impostazioni incluse in% (nome) s."
+msgid "You don't have permission to edit values."
+msgstr "Non hai il permesso di modificare questi valori."
+
+#: templates/livesettings/site_settings.html:26
+# translated = "Non avete il permesso di modificare i valori."
+msgid "Edit Site Settings"
+msgstr "Modifica le impostazioni del sito"
+
+#: templates/livesettings/site_settings.html:59
+# translated = "Modifica impostazioni sito"
+#, python-format
+msgid "Group settings: %(name)s"
+msgstr "Impostazioni di gruppo: %(name)s"
+
+#: templates/livesettings/site_settings.html:86
+msgid "Uncollapse all"
+msgstr "Espandi tutti"
+
+#: templates/livesettings/_admin_site_views.html:5
+msgid "Sites"
+msgstr "Siti"
+
diff --git a/livesettings/locale/ko/LC_MESSAGES/django.mo b/livesettings/locale/ko/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..e0738605
--- /dev/null
+++ b/livesettings/locale/ko/LC_MESSAGES/django.mo
Binary files differ
diff --git a/livesettings/locale/ko/LC_MESSAGES/django.po b/livesettings/locale/ko/LC_MESSAGES/django.po
new file mode 100644
index 00000000..0dbd2d4d
--- /dev/null
+++ b/livesettings/locale/ko/LC_MESSAGES/django.po
@@ -0,0 +1,100 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the Satchmo package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-12-31 00:49-0600\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"
+
+#: values.py:88
+msgid "Base Settings"
+msgstr "기본 세팅"
+
+#: values.py:194
+msgid "Default value: \"\""
+msgstr "기본 값: \"\""
+
+#: values.py:201
+msgid "Default value: "
+msgstr "기본 값: "
+
+#: values.py:204
+#, python-format
+msgid "Default value: %s"
+msgstr "기본 값:%s"
+
+#: templates/livesettings/group_settings.html:10
+#: templates/livesettings/site_settings.html:10
+msgid "Home"
+msgstr "홈"
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:7
+msgid "Log out"
+msgstr "로그 아웃"
+
+#: templates/livesettings/group_settings.html:18
+#: templates/livesettings/site_settings.html:18
+#, fuzzy
+msgid "Please correct the error below."
+msgid_plural "Please correct the errors below."
+msgstr[0] ""
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+msgstr[1] ""
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:7
+msgid "Documentation"
+msgstr "문서"
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:7
+msgid "Change password"
+msgstr "패스워드 변경"
+
+#: templates/livesettings/group_settings.html:11
+msgid "Edit Group Settings"
+msgstr "그룹설정 수정"
+
+#: templates/livesettings/group_settings.html:24
+#, python-format
+msgid "Settings included in %(name)s."
+msgstr "%(name)s을 포함한 설정"
+
+#: templates/livesettings/group_settings.html:49
+#: templates/livesettings/site_settings.html:61
+msgid "You don't have permission to edit values."
+msgstr "이 값을 수정할 권한이 없습니다."
+
+#: templates/livesettings/site_settings.html:11
+msgid "Edit Site Settings"
+msgstr "사이트 설정 수정"
+
+#: templates/livesettings/site_settings.html:34
+#, python-format
+msgid "Group settings: %(name)s"
+msgstr "그룹 설정: %(name)s"
+
diff --git a/livesettings/locale/pl/LC_MESSAGES/django.mo b/livesettings/locale/pl/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..f45e49ed
--- /dev/null
+++ b/livesettings/locale/pl/LC_MESSAGES/django.mo
Binary files differ
diff --git a/livesettings/locale/pl/LC_MESSAGES/django.po b/livesettings/locale/pl/LC_MESSAGES/django.po
new file mode 100644
index 00000000..1e7b4199
--- /dev/null
+++ b/livesettings/locale/pl/LC_MESSAGES/django.po
@@ -0,0 +1,97 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-09-03 18:10+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: <jerzyk@jerzyk.com>\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"
+
+#: templates/livesettings/group_settings.html:10
+#: templates/livesettings/site_settings.html:25
+msgid "Home"
+msgstr "Strona startowa"
+
+#: models.py:76
+#: models.py:115
+msgid "Site"
+msgstr "Strona"
+
+#: values.py:93
+msgid "Base Settings"
+msgstr "Ustawienia podstawowe"
+
+#: values.py:199
+msgid "Default value: \"\""
+msgstr "Domyślna wartość: \"\""
+
+#: values.py:206
+msgid "Default value: "
+msgstr "Domyślna wartość: "
+
+#: values.py:209
+#, python-format
+msgid "Default value: %s"
+msgstr "Domyślna wartość: %s"
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:22
+msgid "Documentation"
+msgstr "Dokumentacja"
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:22
+msgid "Change password"
+msgstr "Zmiana hasła"
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:22
+msgid "Log out"
+msgstr "Wyloguj"
+
+#: templates/livesettings/group_settings.html:11
+msgid "Edit Group Settings"
+msgstr "Edycja Ustawień dla Grupy"
+
+#: templates/livesettings/group_settings.html:18
+#: templates/livesettings/site_settings.html:43
+msgid "Please correct the error below."
+msgid_plural "Please correct the errors below."
+msgstr[0] "Proszę poprawić poniższy błąd."
+msgstr[1] "Proszę poprawić poniższe błędy."
+
+#: templates/livesettings/group_settings.html:24
+#, python-format
+msgid "Settings included in %(name)s."
+msgstr "Ustawienia w %(name)s."
+
+#: templates/livesettings/group_settings.html:49
+#: templates/livesettings/site_settings.html:89
+msgid "You don't have permission to edit values."
+msgstr "Nie masz uprawnień do zmiany tych wartości."
+
+#: templates/livesettings/site_settings.html:26
+msgid "Edit Site Settings"
+msgstr "Edytuj ustawienia serwisu"
+
+#: templates/livesettings/site_settings.html:59
+#, python-format
+msgid "Group settings: %(name)s"
+msgstr "Ustawienia grupy: %(name)s"
+
+#: templates/livesettings/site_settings.html:86
+msgid "Uncollapse all"
+msgstr "Rozwiń wszystko"
+
+#: templates/livesettings/_admin_site_views.html:5
+msgid "Sites"
+msgstr "Strony"
+
diff --git a/livesettings/locale/pt_BR/LC_MESSAGES/django.mo b/livesettings/locale/pt_BR/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..a8bfb8b2
--- /dev/null
+++ b/livesettings/locale/pt_BR/LC_MESSAGES/django.mo
Binary files differ
diff --git a/livesettings/locale/pt_BR/LC_MESSAGES/django.po b/livesettings/locale/pt_BR/LC_MESSAGES/django.po
new file mode 100644
index 00000000..72d49df7
--- /dev/null
+++ b/livesettings/locale/pt_BR/LC_MESSAGES/django.po
@@ -0,0 +1,100 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the PACKAGE package.
+# Terry Laundos Aguiar <terry@s1solucoes.com.br>, 2008.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-09-05 23:50-0300\n"
+"PO-Revision-Date: 2008-09-05 23:51-0300\n"
+"Last-Translator: Terry Laundos Aguiar <terry@s1solucoes.com.br>\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"
+
+#: templates/livesettings/group_settings.html:10
+#: templates/livesettings/site_settings.html:25
+msgid "Home"
+msgstr "Inicial"
+
+#: models.py:76
+#: models.py:115
+#, fuzzy
+msgid "Site"
+msgstr "Estado"
+
+#: values.py:93
+msgid "Base Settings"
+msgstr "Configurações Iniciais"
+
+#: values.py:199
+msgid "Default value: \"\""
+msgstr "Valor padrão: \"\""
+
+#: values.py:206
+msgid "Default value: "
+msgstr "Valor padrão: "
+
+#: values.py:209
+#, python-format
+msgid "Default value: %s"
+msgstr "Valor padrão: %s"
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:22
+msgid "Documentation"
+msgstr "Documentação"
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:22
+msgid "Change password"
+msgstr "Mudar senha"
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:22
+msgid "Log out"
+msgstr "Deslogar"
+
+#: templates/livesettings/group_settings.html:11
+msgid "Edit Group Settings"
+msgstr "Editar preferências de grupo"
+
+#: templates/livesettings/group_settings.html:18
+#: templates/livesettings/site_settings.html:43
+msgid "Please correct the error below."
+msgid_plural "Please correct the errors below."
+msgstr[0] ""
+msgstr[1] ""
+
+#: templates/livesettings/group_settings.html:24
+#, python-format
+msgid "Settings included in %(name)s."
+msgstr "Configurações inclusas no %(name)s."
+
+#: templates/livesettings/group_settings.html:49
+#: templates/livesettings/site_settings.html:89
+msgid "You don't have permission to edit values."
+msgstr "Você não tem permissão para editar valores."
+
+#: templates/livesettings/site_settings.html:26
+msgid "Edit Site Settings"
+msgstr "Editar configurações do site"
+
+#: templates/livesettings/site_settings.html:59
+#, python-format
+msgid "Group settings: %(name)s"
+msgstr "Configurações de grupo: %(name)s"
+
+#: templates/livesettings/site_settings.html:86
+#, fuzzy
+msgid "Uncollapse all"
+msgstr "Desmarcar todos"
+
+#: templates/livesettings/_admin_site_views.html:5
+#, fuzzy
+msgid "Sites"
+msgstr "Notas"
+
diff --git a/livesettings/locale/ru/LC_MESSAGES/django.mo b/livesettings/locale/ru/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..42e6074a
--- /dev/null
+++ b/livesettings/locale/ru/LC_MESSAGES/django.mo
Binary files differ
diff --git a/livesettings/locale/ru/LC_MESSAGES/django.po b/livesettings/locale/ru/LC_MESSAGES/django.po
new file mode 100644
index 00000000..a0db054b
--- /dev/null
+++ b/livesettings/locale/ru/LC_MESSAGES/django.po
@@ -0,0 +1,85 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the Satchmo package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Satchmo\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-12-31 00:49-0600\n"
+"PO-Revision-Date: 2009-03-02 21:52+0300\n"
+"Last-Translator: Данил Семеленов <danil.mail@gmail.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language-Team: \n"
+
+#: values.py:88
+msgid "Base Settings"
+msgstr "Основные настройки"
+
+#: values.py:194
+msgid "Default value: \"\""
+msgstr "Значение по умолчанию: \"\""
+
+#: values.py:201
+msgid "Default value: "
+msgstr "Значение по умолчанию: "
+
+#: values.py:204
+#, python-format
+msgid "Default value: %s"
+msgstr "Значение по умолчанию: %s"
+
+#: templates/livesettings/group_settings.html:10
+#: templates/livesettings/site_settings.html:10
+msgid "Home"
+msgstr ""
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:7
+msgid "Log out"
+msgstr ""
+
+#: templates/livesettings/group_settings.html:18
+#: templates/livesettings/site_settings.html:18
+#, fuzzy
+msgid "Please correct the error below."
+msgid_plural "Please correct the errors below."
+msgstr[0] ""
+msgstr[1] ""
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:7
+msgid "Documentation"
+msgstr ""
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:7
+msgid "Change password"
+msgstr ""
+
+#: templates/livesettings/group_settings.html:11
+msgid "Edit Group Settings"
+msgstr "Изменить группу настроек"
+
+#: templates/livesettings/group_settings.html:24
+#, python-format
+msgid "Settings included in %(name)s."
+msgstr "Настройки включены в %(name)s."
+
+#: templates/livesettings/group_settings.html:49
+#: templates/livesettings/site_settings.html:61
+msgid "You don't have permission to edit values."
+msgstr "У вас нет разрешения изменять значение."
+
+#: templates/livesettings/site_settings.html:11
+msgid "Edit Site Settings"
+msgstr "Изменить настройки сайта"
+
+#: templates/livesettings/site_settings.html:34
+#, python-format
+msgid "Group settings: %(name)s"
+msgstr "Группа настроек: %(name)s"
+
diff --git a/livesettings/locale/sv/LC_MESSAGES/django.mo b/livesettings/locale/sv/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..caed0ab9
--- /dev/null
+++ b/livesettings/locale/sv/LC_MESSAGES/django.mo
Binary files differ
diff --git a/livesettings/locale/sv/LC_MESSAGES/django.po b/livesettings/locale/sv/LC_MESSAGES/django.po
new file mode 100644
index 00000000..6b096f6b
--- /dev/null
+++ b/livesettings/locale/sv/LC_MESSAGES/django.po
@@ -0,0 +1,92 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the PACKAGE package.
+# N.L. <kotorinl@yahoo.co.uk>, 2008.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Satchmo svn\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-04-30 23:40+0200\n"
+"PO-Revision-Date: 2008-04-30 23:35+0100\n"
+"Last-Translator: N.L. <kotorinl@yahoo.co.uk>\n"
+"Language-Team: Group\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: Swedish\n"
+"X-Poedit-Basepath: ../../../\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Poedit-Country: SWEDEN\n"
+
+#: values.py:89
+msgid "Base Settings"
+msgstr "Grundinställningar"
+
+#: values.py:195
+msgid "Default value: \"\""
+msgstr "Förvalt värde: \"\""
+
+#: values.py:202
+msgid "Default value: "
+msgstr "Förvalt värde:"
+
+#: values.py:205
+#, python-format
+msgid "Default value: %s"
+msgstr "Förvalt värde: %s"
+
+#: templates/livesettings/group_settings.html:10
+#: templates/livesettings/site_settings.html:25
+msgid "Home"
+msgstr "Hem"
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:22
+msgid "Log out"
+msgstr "Logga ut"
+
+#: templates/livesettings/group_settings.html:18
+#: templates/livesettings/site_settings.html:41
+msgid "Please correct the error below."
+msgid_plural "Please correct the errors below."
+msgstr[0] "Var god rätta till felet nedan."
+msgstr[1] "Var god rätta till felen nedan."
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:22
+msgid "Documentation"
+msgstr "Dokumentation"
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:22
+msgid "Change password"
+msgstr "Byt lösenord"
+
+#: templates/livesettings/site_settings.html:26
+msgid "Edit Site Settings"
+msgstr "Ändra sajtinställningar"
+
+#: templates/livesettings/group_settings.html:11
+msgid "Edit Group Settings"
+msgstr "Redigera gruppinställningar"
+
+#: templates/livesettings/group_settings.html:24
+#, python-format
+msgid "Settings included in %(name)s."
+msgstr "Inställningar som ingår i %(name)s."
+
+#: templates/livesettings/group_settings.html:49
+#: templates/livesettings/site_settings.html:87
+msgid "You don't have permission to edit values."
+msgstr "Du har inte tillåtelse att ändra värden."
+
+#: templates/livesettings/site_settings.html:58
+#, python-format
+msgid "Group settings: %(name)s"
+msgstr "Gruppinställningar: %(name)s"
+
+#: templates/livesettings/site_settings.html:84
+msgid "Uncollapse all"
+msgstr "Visa alla"
+
diff --git a/livesettings/locale/tr/LC_MESSAGES/django.mo b/livesettings/locale/tr/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..d56ad423
--- /dev/null
+++ b/livesettings/locale/tr/LC_MESSAGES/django.mo
Binary files differ
diff --git a/livesettings/locale/tr/LC_MESSAGES/django.po b/livesettings/locale/tr/LC_MESSAGES/django.po
new file mode 100644
index 00000000..bb2a1506
--- /dev/null
+++ b/livesettings/locale/tr/LC_MESSAGES/django.po
@@ -0,0 +1,102 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the Satchmo package.
+# Selin Çuhadar <selincuhadar@gmail.com>, 2008.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Satchmo\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-12-31 00:49-0600\n"
+"PO-Revision-Date: 2008-06-09 18:18+0200\n"
+"Last-Translator: Selin Çuhadar <selincuhadar@gmail.com>\n"
+"Language-Team: Turkish <selincuhadar@gmail.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Country: TURKEY\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+#: values.py:88
+msgid "Base Settings"
+msgstr "Temel Ayarlar"
+
+#: values.py:194
+msgid "Default value: \"\""
+msgstr "Geçerli Değer: \"\""
+
+#: values.py:201
+msgid "Default value: "
+msgstr "Geçerli Değer:"
+
+#: values.py:204
+#, python-format
+msgid "Default value: %s"
+msgstr "Geçerli Değer: %s"
+
+#: templates/livesettings/group_settings.html:10
+#: templates/livesettings/site_settings.html:10
+msgid "Home"
+msgstr "Ev"
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:7
+msgid "Log out"
+msgstr "Oturumu kapa"
+
+#: templates/livesettings/group_settings.html:18
+#: templates/livesettings/site_settings.html:18
+#, fuzzy
+msgid "Please correct the error below."
+msgid_plural "Please correct the errors below."
+msgstr[0] ""
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+msgstr[1] ""
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"#-#-#-#-# django.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:7
+msgid "Documentation"
+msgstr "Dokümentasyon"
+
+#: templates/livesettings/group_settings.html:7
+#: templates/livesettings/site_settings.html:7
+msgid "Change password"
+msgstr "Şifreyi değiştir"
+
+#: templates/livesettings/group_settings.html:11
+msgid "Edit Group Settings"
+msgstr "Grup Ayarlarını Düzenle"
+
+#: templates/livesettings/group_settings.html:24
+#, python-format
+msgid "Settings included in %(name)s."
+msgstr "%(name)s ayarlara dahil edildi."
+
+#: templates/livesettings/group_settings.html:49
+#: templates/livesettings/site_settings.html:61
+msgid "You don't have permission to edit values."
+msgstr "Değerleri düzenlemek için gerekli izniniz yok."
+
+#: templates/livesettings/site_settings.html:11
+msgid "Edit Site Settings"
+msgstr "Site Ayarlarını Düzenle"
+
+#: templates/livesettings/site_settings.html:34
+#, python-format
+msgid "Group settings: %(name)s"
+msgstr "Grup ayarları: %(name)s"
+
diff --git a/livesettings/models.py b/livesettings/models.py
new file mode 100644
index 00000000..43f14648
--- /dev/null
+++ b/livesettings/models.py
@@ -0,0 +1,170 @@
+from django.conf import settings
+from django.contrib.sites.models import Site
+from django.db import models
+from django.db.models import loading
+from django.utils.translation import ugettext_lazy as _
+from keyedcache import cache_key, cache_get, cache_set, NotCachedError
+from keyedcache.models import CachedObjectMixin
+from livesettings.overrides import get_overrides
+import logging
+
+log = logging.getLogger('configuration.models')
+
+__all__ = ['SettingNotSet', 'Setting', 'LongSetting', 'find_setting']
+
+def _safe_get_siteid(site):
+ if not site:
+ try:
+ site = Site.objects.get_current()
+ siteid = site.id
+ except:
+ siteid = settings.SITE_ID
+ else:
+ siteid = site.id
+ return siteid
+
+def find_setting(group, key, site=None):
+ """Get a setting or longsetting by group and key, cache and return it."""
+
+ siteid = _safe_get_siteid(site)
+ setting = None
+
+ use_db, overrides = get_overrides(siteid)
+ ck = cache_key('Setting', siteid, group, key)
+
+ if use_db:
+ try:
+ setting = cache_get(ck)
+
+ except NotCachedError, nce:
+ if loading.app_cache_ready():
+ try:
+ setting = Setting.objects.get(site__id__exact=siteid, key__exact=key, group__exact=group)
+
+ except Setting.DoesNotExist:
+ # maybe it is a "long setting"
+ try:
+ setting = LongSetting.objects.get(site__id__exact=siteid, key__exact=key, group__exact=group)
+
+ except LongSetting.DoesNotExist:
+ pass
+
+ cache_set(ck, value=setting)
+
+ else:
+ grp = overrides.get(group, None)
+ if grp and grp.has_key(key):
+ val = grp[key]
+ setting = ImmutableSetting(key=key, group=group, value=val)
+ log.debug('Returning overridden: %s', setting)
+
+ if not setting:
+ raise SettingNotSet(key, cachekey=ck)
+
+ return setting
+
+class SettingNotSet(Exception):
+ def __init__(self, k, cachekey=None):
+ self.key = k
+ self.cachekey = cachekey
+ self.args = [self.key, self.cachekey]
+
+class SettingManager(models.Manager):
+ def get_query_set(self):
+ all = super(SettingManager, self).get_query_set()
+ siteid = _safe_get_siteid(None)
+ return all.filter(site__id__exact=siteid)
+
+
+class ImmutableSetting(object):
+
+ def __init__(self, group="", key="", value="", site=1):
+ self.site = site
+ self.group = group
+ self.key = key
+ self.value = value
+
+ def cache_key(self, *args, **kwargs):
+ return cache_key('OverrideSetting', self.site, self.group, self.key)
+
+ def delete(self):
+ pass
+
+ def save(self, *args, **kwargs):
+ pass
+
+ def __repr__(self):
+ return "ImmutableSetting: %s.%s=%s" % (self.group, self.key, self.value)
+
+
+class Setting(models.Model, CachedObjectMixin):
+ site = models.ForeignKey(Site, verbose_name=_('Site'))
+ group = models.CharField(max_length=100, blank=False, null=False)
+ key = models.CharField(max_length=100, blank=False, null=False)
+ value = models.CharField(max_length=255, blank=True)
+
+ objects = SettingManager()
+
+ def __nonzero__(self):
+ return self.id is not None
+
+ def cache_key(self, *args, **kwargs):
+ return cache_key('Setting', self.site, self.group, self.key)
+
+ def delete(self):
+ self.cache_delete()
+ super(Setting, self).delete()
+
+ def save(self, force_insert=False, force_update=False):
+ try:
+ site = self.site
+ except Site.DoesNotExist:
+ self.site = Site.objects.get_current()
+
+ super(Setting, self).save(force_insert=force_insert, force_update=force_update)
+
+ self.cache_set()
+
+ class Meta:
+ unique_together = ('site', 'group', 'key')
+
+
+class LongSettingManager(models.Manager):
+ def get_query_set(self):
+ all = super(LongSettingManager, self).get_query_set()
+ siteid = _safe_get_siteid(None)
+ return all.filter(site__id__exact=siteid)
+
+class LongSetting(models.Model, CachedObjectMixin):
+ """A Setting which can handle more than 255 characters"""
+ site = models.ForeignKey(Site, verbose_name=_('Site'))
+ group = models.CharField(max_length=100, blank=False, null=False)
+ key = models.CharField(max_length=100, blank=False, null=False)
+ value = models.TextField(blank=True)
+
+ objects = LongSettingManager()
+
+ def __nonzero__(self):
+ return self.id is not None
+
+ def cache_key(self, *args, **kwargs):
+ # note same cache pattern as Setting. This is so we can look up in one check.
+ # they can't overlap anyway, so this is moderately safe. At the worst, the
+ # Setting will override a LongSetting.
+ return cache_key('Setting', self.site, self.group, self.key)
+
+ def delete(self):
+ self.cache_delete()
+ super(LongSetting, self).delete()
+
+ def save(self, force_insert=False, force_update=False):
+ try:
+ site = self.site
+ except Site.DoesNotExist:
+ self.site = Site.objects.get_current()
+ super(LongSetting, self).save(force_insert=force_insert, force_update=force_update)
+ self.cache_set()
+
+ class Meta:
+ unique_together = ('site', 'group', 'key')
+
diff --git a/livesettings/overrides.py b/livesettings/overrides.py
new file mode 100644
index 00000000..5f88d5c5
--- /dev/null
+++ b/livesettings/overrides.py
@@ -0,0 +1,55 @@
+"""Allows livesettings to be "locked down" and no longer use the settings page or the database
+for settings retrieval.
+"""
+
+from django.conf import settings as djangosettings
+from django.contrib.sites.models import Site
+import logging
+
+__all__ = ['get_overrides']
+
+def _safe_get_siteid(site):
+ if not site:
+ try:
+ site = Site.objects.get_current()
+ siteid = site.id
+ except:
+ siteid = djangosettings.SITE_ID
+ else:
+ siteid = site.id
+ return siteid
+
+def get_overrides(siteid=-1):
+ """Check to see if livesettings is allowed to use the database. If not, then
+ it will only use the values in the dictionary, LIVESETTINGS_OPTIONS[SITEID]['SETTINGS'],
+ this allows 'lockdown' of a live site.
+
+ The LIVESETTINGS dict must be formatted as follows::
+
+ LIVESETTINGS_OPTIONS = {
+ 1 : {
+ 'DB' : [True/False],
+ SETTINGS = {
+ 'GROUPKEY' : {'KEY', val, 'KEY2', val},
+ 'GROUPKEY2' : {'KEY', val, 'KEY2', val},
+ }
+ }
+ }
+
+ In the settings dict above, the "val" entries must exactly match the format
+ stored in the database for a setting. Do not use a literal True or an integer,
+ it needs to be the string representation of them.
+
+ Returns a tuple (DB_ALLOWED, SETTINGS)
+ """
+ overrides = (True, {})
+ if hasattr(djangosettings, 'LIVESETTINGS_OPTIONS'):
+ if siteid == -1:
+ siteid = _safe_get_siteid(None)
+
+ opts = djangosettings.LIVESETTINGS_OPTIONS
+ if opts.has_key(siteid):
+ opts = opts[siteid]
+ overrides = (opts.get('DB', True), opts['SETTINGS'])
+
+ return overrides
diff --git a/livesettings/signals.py b/livesettings/signals.py
new file mode 100644
index 00000000..ddea31f5
--- /dev/null
+++ b/livesettings/signals.py
@@ -0,0 +1,3 @@
+import django.dispatch
+
+configuration_value_changed = django.dispatch.Signal()
diff --git a/livesettings/templates/livesettings/_admin_site_views.html b/livesettings/templates/livesettings/_admin_site_views.html
new file mode 100644
index 00000000..17d08f58
--- /dev/null
+++ b/livesettings/templates/livesettings/_admin_site_views.html
@@ -0,0 +1,15 @@
+{% load i18n %}
+<div id="content-related">
+ <div class="module" id="sites-module">
+ <h2 class="module-title">{% trans 'Sites' %}</h2>
+ <div class="module-content">
+ <ul>
+ {% for label, link in links %}
+ <li>
+ <a href="{{ link }}">{{ label }}</a>
+ </li>
+ {% endfor %}
+ </ul>
+ </div>
+ </div>
+</div>
diff --git a/livesettings/templates/livesettings/group_settings.html b/livesettings/templates/livesettings/group_settings.html
new file mode 100644
index 00000000..e56f764f
--- /dev/null
+++ b/livesettings/templates/livesettings/group_settings.html
@@ -0,0 +1,81 @@
+{% extends "admin/base_site.html" %}
+{% load i18n admin_modify config_tags %}
+{% block extrastyle %}
+{{ block.super }}
+<link rel="stylesheet" type="text/css" href="{% load adminmedia %}{% admin_media_prefix %}css/base.css" />
+{% endblock %}
+
+{% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/forms.css{% endblock %}
+{% block coltype %}colMS{% endblock %}
+{% block bodyclass %}dashboard{% endblock %}
+{% block userlinks %}<a href="/admin/doc/">{% trans 'Documentation' %}</a> / <a href="/admin/password_change/">{% trans 'Change password' %}</a> / <a href="/admin/logout/">{% trans 'Log out' %}</a>{% endblock %}
+{% block breadcrumbs %}{% if not is_popup %}
+<div class="breadcrumbs">
+ <a href="/admin/">{% trans "Home" %}</a> &rsaquo;
+ {% trans "Edit Group Settings" %}
+</div>
+{% endif %}{% endblock %}
+{% block content %}
+<div id="content-main">
+{% if form.errors %}
+ <p class="errornote">
+ {% blocktrans count form.errors|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
+ </p>
+{% endif %}
+{% if form.fields %}
+<form method="post">
+ <div class="module">
+ <table summary="{% filter capfirst %}{% blocktrans with group.name as name %}Settings included in {{ name }}.{% endblocktrans %}{% endfilter %}" width="100%">
+ {% for field in form %}
+ {% if field.is_hidden %}
+ <!-- skip hidden field {{field.key}} -->
+ {% else %}
+ {% if field.errors %}
+ <tr class="error">
+ <td colspan="2">{{ field.errors }}</td>
+ </tr>
+ {% endif %}
+ <tr{% if field.errors %} class="error"{% endif %}>
+ <td style="width: 50%;">
+ {{ field.label_tag }}
+ {% if field.help_text %}
+ <p class="help">{{ field.help_text|safe }}</p>
+ {% endif %}
+ {% if field.field.default_text %}
+ <p class="help">{{ field.field.default_text|safe }}</p>
+ {% endif %}
+ </td>
+ <td>{{ field }}</td>
+ </tr>
+ {% endif %}
+ {% endfor %}
+ </table>
+ {% for field in form %}
+ {% if field.is_hidden %}
+ {{field}}
+ {% endif %}
+ {% endfor %}
+ </div>
+ <input type="submit" value="Save" class="default" />
+</form>
+{% else %}
+ <p>{% trans "You don't have permission to edit values." %}</p>
+{% endif %}
+</div>
+{% if all_groups %}
+<div id="content-related">
+ <div class="module">
+ <h2>{% trans "Setting groups" %}</h2>
+ <ul>
+ {% for g in all_groups %}
+ {% ifequal g.key group.key %}
+ <li><b>{{g.name}}</b></li>
+ {% else %}
+ <li><a href="{% url group_settings g.key %}">{{g.name}}</a></li>
+ {% endifequal %}
+ {% endfor %}
+ </ul>
+ </div>
+</div>
+{% endif %}
+{% endblock %}
diff --git a/livesettings/templates/livesettings/site_settings.html b/livesettings/templates/livesettings/site_settings.html
new file mode 100644
index 00000000..35333778
--- /dev/null
+++ b/livesettings/templates/livesettings/site_settings.html
@@ -0,0 +1,101 @@
+{% extends "admin/base_site.html" %}
+{% load i18n admin_modify config_tags %}
+
+{% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/forms.css{% endblock %}
+{% block extrahead %}
+<script type="text/javascript" src="{% url admin:jsi18n %}"></script>
+<script type="text/javascript" src="{% admin_media_prefix %}js/core.js"></script>
+<script type="text/javascript" src="{% admin_media_prefix %}js/admin/CollapsedFieldsets.js"></script>
+{% endblock %}
+{% block extrastyle %}
+{{ block.super }}
+<link rel="stylesheet" type="text/css" href="{% load adminmedia %}{% admin_media_prefix %}css/base.css" />
+<style type="text/css">
+ul.fieldref { margin: 0; padding: 0; font-size: 9px; }
+ul.fieldref li { float: left; margin: 0 10px 0 0; list-style: none; }
+fieldset.collapsed h2 { display: block !important; }
+fieldset.collapsed h2 a { display: inline !important; }
+div.fieldcontainer { float: left; margin-right: 0; }
+</style>
+{% endblock %}
+{% block coltype %}colMS{% endblock %}
+{% block bodyclass %}dashboard{% endblock %}
+{% block userlinks %}<a href="/admin/doc/">{% trans 'Documentation' %}</a> / <a href="/admin/password_change/">{% trans 'Change password' %}</a> / <a href="/admin/logout/">{% trans 'Log out' %}</a>{% endblock %}
+{% block breadcrumbs %}{% if not is_popup %}
+<div class="breadcrumbs">
+ <a href="/admin/">{% trans "Home" %}</a> &rsaquo;
+ {% trans "Edit Site Settings" %}
+</div>
+{% endif %}{% endblock %}
+{% block content %}
+{% comment %}
+<div class="fieldcontainer">
+<ul class="fieldref">
+{% for group in form.groups %}
+ <li><a onclick="javascript:CollapsedFieldsets.show({{ forloop.counter0 }});" href="#{{ group.key }}">{{ group.name }}</a></li>
+{% endfor %}
+</ul>
+</div>
+{% endcomment %}
+<span style="clear: both;" />
+<div id="content-main">
+{% if not use_db %}
+ <p>{% trans "Livesettings are disabled for this site." %}</p>
+ <p>{% trans "All configuration options must be edited in the site settings.py file" %}</p>
+ </div>
+ {% admin_site_views 'satchmo_site_settings' %}
+{% else %}
+ {% if form.errors %}
+ <p class="errornote">
+ {% blocktrans count form.errors|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
+ </p>
+ {% endif %}
+ {% if form.fields %}
+ <form method="post">
+ {% for field in form %}
+ {% if field.is_hidden %}
+ {{ field }}
+ {% else %}
+ {% ifchanged field.field.group %}{% with field.field.group as group %}
+ {% if not forloop.first %}
+ </table>
+ </fieldset>
+ {% endif %}
+ <fieldset class="module collapse">
+ <h2 id="{{ group.key }}">{{ group.name }}</h2>
+ <table summary="{% blocktrans with group.name as name %}Group settings: {{ name }}{% endblocktrans %}" style="width: 100%">
+ {% endwith %}{% endifchanged %}
+
+ {% if field.errors %}
+ <tr class="error">
+ <td colspan="2">{{ field.errors }}</td>
+ </tr>
+ {% endif %}
+ <tr{% if field.errors %} class="error"{% endif %}>
+ <td style="width: 50%;">
+ {{ field.label_tag }}
+ {% if field.help_text %}
+ <p class="help">{{ field.help_text|break_at:40|safe }}</p>
+ {% endif %}
+ {% if field.field.default_text %}
+ <p class="help">{{ field.field.default_text|break_at:40}}</p>
+ {% endif %}
+ </td>
+ <td>{{ field }}</td>
+ </tr>
+ {% endif %}
+ {% endfor %}
+ </table>
+ </div>
+ {% admin_site_views 'satchmo_site_settings' %}
+ <br class="clear:both;" />
+ <input type="submit" value="Save" class="default" />
+ <p><a onclick="javascript:CollapsedFieldsets.uncollapse_all(); return false;" href="#">{% trans 'Uncollapse all' %}</a></p>
+ <p><a href="{% url settings_export %}">Export</a></p>
+ </form>
+ {% else %}
+ <p>{% trans "You don't have permission to edit values." %}</p>
+ {% endif %}
+{% endif %}
+</div>
+{% endblock %}
diff --git a/livesettings/templates/livesettings/text.txt b/livesettings/templates/livesettings/text.txt
new file mode 100644
index 00000000..d57a57e3
--- /dev/null
+++ b/livesettings/templates/livesettings/text.txt
@@ -0,0 +1 @@
+{{ text|safe }} \ No newline at end of file
diff --git a/livesettings/templatetags/__init__.py b/livesettings/templatetags/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/livesettings/templatetags/__init__.py
diff --git a/livesettings/templatetags/config_tags.py b/livesettings/templatetags/config_tags.py
new file mode 100644
index 00000000..bcdded12
--- /dev/null
+++ b/livesettings/templatetags/config_tags.py
@@ -0,0 +1,91 @@
+from django import template
+from django.contrib.sites.models import Site
+from django.core import urlresolvers
+from livesettings import config_value
+from livesettings.utils import url_join
+import logging
+
+log = logging.getLogger('configuration.config_tags')
+
+register = template.Library()
+
+def force_space(value, chars=40):
+ """Forces spaces every `chars` in value"""
+
+ chars = int(chars)
+ if len(value) < chars:
+ return value
+ else:
+ out = []
+ start = 0
+ end = 0
+ looping = True
+
+ while looping:
+ start = end
+ end += chars
+ out.append(value[start:end])
+ looping = end < len(value)
+
+ return ' '.join(out)
+
+def break_at(value, chars=40):
+ """Force spaces into long lines which don't have spaces"""
+ #todo: EF - lazy patch
+ return value
+
+ chars = int(chars)
+ value = unicode(value)
+ if len(value) < chars:
+ return value
+ else:
+ out = []
+ line = value.split(' ')
+ for word in line:
+ if len(word) > chars:
+ out.append(force_space(word, chars))
+ else:
+ out.append(word)
+
+ return " ".join(out)
+
+register.filter('break_at', break_at)
+
+def config_boolean(option):
+ """Looks up the configuration option, returning true or false."""
+ args = option.split('.')
+ try:
+ val = config_value(*args)
+ except:
+ log.warn('config_boolean tag: Tried to look up config setting "%s", got SettingNotSet, returning False', option)
+ val = False
+ if val:
+ return "true"
+ else:
+ return ""
+
+register.filter('config_boolean', config_boolean)
+
+def admin_site_views(view):
+ """Returns a formatted list of sites, rendering for view, if any"""
+
+ if view:
+ path = urlresolvers.reverse(view)
+ else:
+ path = None
+
+ links = []
+ for site in Site.objects.all():
+ paths = ["http://", site.domain]
+ if path:
+ paths.append(path)
+
+ links.append((site.name, url_join(paths)))
+
+ ret = {
+ 'links' : links,
+ }
+ return ret
+
+
+register.inclusion_tag('livesettings/_admin_site_views.html')(admin_site_views)
diff --git a/livesettings/tests.py b/livesettings/tests.py
new file mode 100644
index 00000000..2a60bf7e
--- /dev/null
+++ b/livesettings/tests.py
@@ -0,0 +1,545 @@
+from django.conf import settings as djangosettings
+from django.test import TestCase
+import keyedcache
+from livesettings import *
+import logging
+log = logging.getLogger('test');
+
+class ConfigurationFunctionTest(TestCase):
+
+ def testSetSingleConfigItem(self):
+ value = IntegerValue(BASE_GROUP, 'SingleItem')
+ config_register(value)
+ self.assert_(config_exists(BASE_GROUP, 'SingleItem'))
+
+ def testSetTwoConfigItems(self):
+ s = [IntegerValue(BASE_GROUP, 'testTwoA'), StringValue(BASE_GROUP, 'testTwoB')]
+ config_register_list(*s)
+
+ self.assert_(config_exists(BASE_GROUP, 'testTwoA'))
+ self.assert_(config_exists(BASE_GROUP, 'testTwoB'))
+
+ def testSetGroup(self):
+ g1 = ConfigurationGroup('test1','test1')
+ value = IntegerValue(g1, 'SingleGroupedItem')
+ config_register(value)
+ self.assertFalse(config_exists(BASE_GROUP, 'SingleGroupedItem'))
+ self.assert_(config_exists(g1, 'SingleGroupedItem'))
+
+
+class ConfigurationTestSettings(TestCase):
+
+ def setUp(self):
+ # clear out cache from previous runs
+ keyedcache.cache_delete()
+ g = ConfigurationGroup('test2','test2')
+ self.g = g
+ config_register(StringValue(g, 's1'))
+ config_register(IntegerValue(g, 's2', default=10))
+ config_register(IntegerValue(g, 's3', default=10))
+
+ def testSetSetting(self):
+ c = config_get('test2', 's1')
+ c.update('test')
+
+ self.assertEqual(c.value, 'test')
+ self.assertEqual(c.setting.value, 'test')
+
+ def testSettingDefault(self):
+ c = config_get('test2', 's2')
+ self.assertEqual(c.value, 10)
+
+ def testSetAndReset(self):
+ """Test setting one value and then updating"""
+ c = config_get('test2', 's1')
+ c.update('test1')
+
+ self.assertEqual(c.value, 'test1')
+
+ # should be true, since it is an update
+ self.assert_(c.update('test2'))
+ self.assertEqual(c.value, 'test2')
+
+ def testTwice(self):
+ """Config items should respond False to duplicate requests to update."""
+
+ c = config_get('test2', 's1')
+ c.update('test1')
+
+ self.assertFalse(c.update('test1'))
+
+
+ def testDeletesDefault(self):
+ c = config_get('test2', 's3')
+ # false because it isn't saving a default value
+ self.assertFalse(c.update(10))
+
+ self.assert_(c.update(20))
+ self.assertEqual(c.value, 20)
+ try:
+ s = c.setting
+ except SettingNotSet:
+ self.fail("Should have a setting now")
+
+ # now delete and go back to no setting by setting the default
+ self.assert_(c.update(10))
+ self.assertEqual(c.value, 10)
+
+ try:
+ s = c.setting
+ self.fail('Should throw SettingNotSet')
+ except SettingNotSet:
+ pass
+
+
+class ConfigTestDotAccess(TestCase):
+ def setUp(self):
+ # clear out cache from previous runs
+ keyedcache.cache_delete()
+
+ g = ConfigurationGroup('test3','test3')
+ self.g = g
+ c1 = config_register(BooleanValue(g, 's1', default=True))
+ c2 = config_register(IntegerValue(g, 's2', default=10))
+ c2.update(100)
+
+ def testDotAccess(self):
+ self.assert_(ConfigurationSettings().test3.s1.value)
+ self.assertEqual(ConfigurationSettings().test3.s2.value, 100)
+
+ def testSettingProperty(self):
+ c = config_get('test3','s2')
+ s = c.setting
+ self.assert_(s.value, 100)
+
+ def testDictValues(self):
+ d = self.g.dict_values()
+ self.assertEqual(d, {'s1': True, 's2' : 100})
+
+class ConfigTestModuleValue(TestCase):
+ def setUp(self):
+ # clear out cache from previous runs
+ keyedcache.cache_delete()
+
+ g = ConfigurationGroup('modules','module test')
+ self.g = g
+ self.c = config_register(ModuleValue(g, 'test'))
+
+ # def testModule(self):
+ # c = config_get('modules', 'test')
+ # c.update('satchmo_store')
+
+ # self.assert_(hasattr(self.c.value, 'get_version'))
+
+class ConfigTestSortOrder(TestCase):
+ def setUp(self):
+ # clear out cache from previous runs
+ keyedcache.cache_delete()
+
+ g1 = ConfigurationGroup('group1', 'Group 1', ordering=-1001)
+ g2 = ConfigurationGroup('group2', 'Group 2', ordering=-1002)
+ g3 = ConfigurationGroup('group3', 'Group 3', ordering=-1003)
+
+ self.g1 = g1
+ self.g2 = g2
+ self.g3 = g3
+
+ self.g1c1 = config_register(IntegerValue(g1, 'c1'))
+ self.g1c2 = config_register(IntegerValue(g1, 'c2'))
+ self.g1c3 = config_register(IntegerValue(g1, 'c3'))
+
+ self.g2c1 = config_register(IntegerValue(g2, 'c1'))
+ self.g2c2 = config_register(IntegerValue(g2, 'c2'))
+ self.g2c3 = config_register(IntegerValue(g2, 'c3'))
+
+ self.g3c1 = config_register(IntegerValue(g3, 'c1'))
+ self.g3c2 = config_register(IntegerValue(g3, 'c2'))
+ self.g3c3 = config_register(IntegerValue(g3, 'c3'))
+
+ def testGroupOrdering(self):
+ mgr = ConfigurationSettings()
+ self.assertEqual(mgr[2].key, self.g1.key)
+ self.assertEqual(mgr[1].key, self.g2.key)
+ self.assertEqual(mgr[0].key, self.g3.key)
+
+
+class TestMultipleValues(TestCase):
+
+ def setUp(self):
+ # clear out cache from previous runs
+ keyedcache.cache_delete()
+
+ g1 = ConfigurationGroup('m1', 'Multiple Group 1', ordering=1000)
+ self.g1 = g1
+
+ self.g1c1 = config_register(MultipleStringValue(g1,
+ 'c1',
+ choices=((1,'one'),(2,'two'),(3,'three'))))
+
+ def testSave(self):
+
+ c = config_get('m1','c1')
+ c.update([1,2])
+ self.assertEqual(c.value, [1,2])
+
+ def testAddChoice(self):
+
+ config_add_choice('m1','c1',(4, 'four'))
+ c = config_get('m1','c1')
+ self.assertEqual(c.choices, ((1,'one'),(2,'two'),(3,'three'),(4,'four')))
+
+ def testChoiceValues(self):
+ self.g1c1.update([1,2])
+
+ self.assertEqual(self.g1c1.value, [1,2])
+ self.assertEqual(self.g1c1.choice_values, [(1, 'one'),(2, 'two')])
+
+ choices = config_choice_values('m1', 'c1')
+ self.assertEqual(choices, [(1, 'one'),(2, 'two')])
+
+class TestMultipleValuesWithDefault(TestCase):
+
+ def setUp(self):
+ # clear out cache from previous runs
+ keyedcache.cache_delete()
+
+ g1 = ConfigurationGroup('mv2', 'Multiple Group 2', ordering=1000)
+ self.g1 = g1
+
+ self.g1c1 = config_register(MultipleStringValue(g1,
+ 'c1',
+ choices=((1,'one'),(2,'two'),(3,'three')),
+ default=[1,2]))
+
+ def testDefault(self):
+
+ c = config_get('mv2','c1')
+ self.assertEqual(c.value, [1,2])
+
+ c.update([1,2,3])
+ self.assertEqual(c.value, [1,2,3])
+
+class ConfigTestChoices(TestCase):
+
+ def testAddPreregisteredChoice(self):
+ """Test that we can register choices before the config is actually set up."""
+ config_add_choice('ctg1', 'c1', ('a', 'Item A'))
+ config_add_choice('ctg1', 'c1', ('b', 'Item B'))
+ config_add_choice('ctg1', 'c1', ('c', 'Item C'))
+
+ g1 = ConfigurationGroup('ctg1', 'Choice 1', ordering=1000)
+ config_register(StringValue(g1, 'c1'))
+
+ c = config_get('ctg1','c1')
+
+ self.assertEqual(c.choices, [('a','Item A'), ('b','Item B'), ('c','Item C')])
+
+
+class ConfigTestRequires(TestCase):
+
+ def setUp(self):
+ # clear out cache from previous runs
+ keyedcache.cache_delete()
+
+ g1 = ConfigurationGroup('req1', 'Requirements 1', ordering=1000)
+
+ self.g1 = g1
+
+ bool1 = config_register(BooleanValue(g1, 'bool1', default=False, ordering=1))
+ bool2 = config_register(BooleanValue(g1, 'bool2', ordering=2))
+
+ self.g1c1 = config_register(IntegerValue(g1, 'c1', requires=bool1, ordering=3))
+
+ self.g1c2 = config_register(IntegerValue(g1, 'c2', requires=bool2, ordering=4))
+ self.g1c3 = config_register(IntegerValue(g1, 'c3', ordering=5))
+
+ bool2.update(True)
+
+ def testSimpleRequires(self):
+
+ v = config_value('req1', 'bool2')
+ self.assertTrue(v)
+
+ keys = [cfg.key for cfg in self.g1]
+ self.assertEqual(keys, ['bool1', 'bool2', 'c2','c3'])
+
+ c = config_get('req1','bool1')
+ c.update(True)
+
+ keys = [cfg.key for cfg in self.g1]
+ self.assertEqual(keys, ['bool1', 'bool2', 'c1', 'c2', 'c3'])
+
+class ConfigTestRequiresChoices(TestCase):
+
+ def setUp(self):
+ # clear out cache from previous runs
+ keyedcache.cache_delete()
+
+ g1 = ConfigurationGroup('req2', 'Requirements 2', ordering=1000)
+
+ self.g1 = g1
+
+ choices1 = config_register(MultipleStringValue(BASE_GROUP, 'rc1', ordering=1))
+
+ self.g1c1 = config_register(IntegerValue(g1, 'c1', requires=choices1, ordering=3))
+ self.g1c2 = config_register(IntegerValue(g1, 'c2', requires=choices1, ordering=4))
+ self.g1c3 = config_register(IntegerValue(g1, 'c3', ordering=5))
+
+ choices1.update('c1')
+
+ g2 = ConfigurationGroup('req3', 'Requirements 3', ordering=1000)
+
+ self.g2 = g2
+
+ choices2 = config_register(StringValue(BASE_GROUP, 'choices2', ordering=1))
+
+ self.g2c1 = config_register(IntegerValue(g2, 'c1', requires=choices2, ordering=3))
+ self.g2c2 = config_register(IntegerValue(g2, 'c2', requires=choices2, ordering=4))
+ self.g2c3 = config_register(IntegerValue(g2, 'c3', requires=choices2, ordering=5))
+
+ choices2.update('c1')
+
+ def testSimpleRequiresChoices(self):
+
+ v = config_value('BASE', 'rc1')
+ self.assertEquals(v, ['c1'])
+
+ g = config_get_group('req2')
+ keys = [cfg.key for cfg in g]
+ self.assertEqual(keys, ['c1','c3'])
+
+ c = config_get('BASE', 'rc1')
+ c.update(['c1','c2'])
+
+ g = config_get_group('req2')
+ keys = [cfg.key for cfg in g]
+ self.assertEqual(keys, ['c1', 'c2', 'c3'])
+
+ def testRequiresSingleValue(self):
+ v = config_value('BASE', 'choices2')
+ self.assertEquals(v, 'c1')
+
+ keys = [cfg.key for cfg in self.g2]
+ self.assertEqual(keys, ['c1'])
+
+ c = config_get('BASE', 'choices2')
+ c.update('c2')
+
+ keys = [cfg.key for cfg in self.g2]
+ self.assertEqual(keys, ['c2'])
+
+class ConfigTestRequiresValue(TestCase):
+
+ def setUp(self):
+ # clear out cache from previous runs
+ keyedcache.cache_delete()
+
+ g1 = ConfigurationGroup('reqval', 'Requirements 3', ordering=1000)
+
+ self.g1 = g1
+
+ choices1 = config_register(MultipleStringValue(BASE_GROUP, 'valchoices', ordering=1))
+
+ self.g1c1 = config_register(IntegerValue(g1, 'c1', requires=choices1, requiresvalue='foo', ordering=3))
+ self.g1c2 = config_register(IntegerValue(g1, 'c2', requires=choices1, requiresvalue='bar', ordering=4))
+ self.g1c3 = config_register(IntegerValue(g1, 'c3', ordering=5))
+
+ choices1.update('foo')
+
+ g2 = ConfigurationGroup('reqval2', 'Requirements 4', ordering=1000)
+
+ self.g2 = g2
+
+ choices2 = config_register(StringValue(BASE_GROUP, 'valchoices2', ordering=1,
+ choices=(('a','test a'),('b', 'test b'),('c', 'test c'))))
+
+ self.g2c1 = config_register(IntegerValue(g2, 'c1', requires=choices2, requiresvalue='a', ordering=3))
+ self.g2c2 = config_register(IntegerValue(g2, 'c2', requires=choices2, requiresvalue='b', ordering=4))
+ self.g2c3 = config_register(IntegerValue(g2, 'c3', requires=choices2, requiresvalue='c', ordering=5))
+
+ choices2.update('a')
+
+ def testRequiresValue(self):
+ v = config_value('BASE', 'valchoices')
+ self.assertEquals(v, ['foo'])
+
+ g = config_get_group('reqval')
+
+ keys = [cfg.key for cfg in g]
+ self.assertEqual(keys, ['c1','c3'])
+
+ c = config_get('BASE', 'valchoices')
+ c.update(['foo','bar'])
+
+ g = config_get_group('reqval')
+ keys = [cfg.key for cfg in g]
+ self.assertEqual(keys, ['c1', 'c2', 'c3'])
+
+ def testRequiresSingleValue(self):
+ v = config_value('BASE', 'valchoices2')
+ self.assertEquals(v, 'a')
+
+ keys = [cfg.key for cfg in self.g2]
+ self.assertEqual(keys, ['c1'])
+
+ c = config_get('BASE', 'valchoices2')
+ c.update('b')
+
+ keys = [cfg.key for cfg in self.g2]
+ self.assertEqual(keys, ['c2'])
+
+class ConfigTestGroupRequires(TestCase):
+ def setUp(self):
+ # clear out cache from previous runs
+ keyedcache.cache_delete()
+
+ choices1 = config_register(MultipleStringValue(BASE_GROUP, 'groupchoice', ordering=1))
+ choices2 = config_register(MultipleStringValue(BASE_GROUP, 'groupchoice2', ordering=1))
+
+ g1 = ConfigurationGroup('groupreq', 'Requirements 4', ordering=1000, requires=choices1)
+ self.g1 = g1
+
+ self.g1c1 = config_register(IntegerValue(g1, 'c1', ordering=3))
+ self.g1c2 = config_register(IntegerValue(g1, 'c2', requires=choices2, requiresvalue='bar', ordering=4))
+ self.g1c3 = config_register(IntegerValue(g1, 'c3', ordering=5))
+
+ def testRequiresValue(self):
+ c = config_get('BASE', 'groupchoice')
+ self.assertEquals(c.value, [])
+
+ keys = [cfg.key for cfg in self.g1]
+ self.assertEqual(keys, [])
+
+ c2 = config_get('BASE', 'groupchoice2')
+ c2.update('bar')
+
+ keys = [cfg.key for cfg in self.g1]
+ self.assertEqual(keys, ['c2'])
+
+ c.update(['groupreq'])
+
+ keys = [cfg.key for cfg in self.g1]
+ self.assertEqual(keys, ['c1', 'c2', 'c3'])
+
+class ConfigCollectGroup(TestCase):
+ def setUp(self):
+ keyedcache.cache_delete()
+ choices = config_register(MultipleStringValue(BASE_GROUP, 'collect', ordering=1))
+ self.choices = choices
+
+ g1 = ConfigurationGroup('coll1', 'Collection 1')
+ g2 = ConfigurationGroup('coll2', 'Collection 2')
+ g3 = ConfigurationGroup('coll3', 'Collection 3')
+
+ g1c1 = config_register(StringValue(g1, 'test'))
+ g1c2 = config_register(StringValue(g1, 'test1'))
+ g2c1 = config_register(StringValue(g2, 'test'))
+ g3c1 = config_register(StringValue(g3, 'test'))
+
+ g1c1.update('set a')
+ g1c2.update('set b')
+ g2c1.update('set a')
+ g3c1.update('set d')
+
+ choices.update(['coll1','coll3'])
+
+ def testCollectSimple(self):
+ v = config_collect_values('BASE', 'collect', 'test')
+
+ self.assertEqual(v, ['set a', 'set d'])
+
+ def testCollectUnique(self):
+ self.choices.update(['coll1','coll2','coll3'])
+
+ v = config_collect_values('BASE', 'collect', 'test', unique=False)
+
+ self.assertEqual(v, ['set a', 'set a', 'set d'])
+
+ v = config_collect_values('BASE', 'collect', 'test', unique=True)
+
+ self.assertEqual(v, ['set a', 'set d'])
+
+class LongSettingTest(TestCase):
+ def setUp(self):
+ keyedcache.cache_delete()
+ wide = config_register(LongStringValue(BASE_GROUP, 'LONG', ordering=1, default="woot"))
+ self.wide = wide
+ self.wide.update('*' * 1000)
+
+ def testLongStorage(self):
+ w = config_value('BASE', 'LONG')
+ self.assertEqual(len(w), 1000)
+ self.assertEqual(w, '*'*1000)
+
+ def testShortInLong(self):
+ self.wide.update("test")
+ w = config_value('BASE', 'LONG')
+ self.assertEqual(len(w), 4)
+ self.assertEqual(w, 'test')
+
+ def testDelete(self):
+ remember = self.wide.setting.id
+ self.wide.update('woot')
+
+ try:
+ q = LongSetting.objects.get(pk = remember)
+ self.fail("Should be deletec")
+ except LongSetting.DoesNotExist:
+ pass
+
+class OverrideTest(TestCase):
+ """Test settings overrides"""
+ def setUp(self):
+ # clear out cache from previous runs
+ keyedcache.cache_delete()
+
+ djangosettings.LIVESETTINGS_OPTIONS = {
+ 1 : {
+ 'DB' : False,
+ 'SETTINGS' : {
+ 'overgroup' : {
+ 's2' : '100',
+ 'choices' : '["one","two","three"]'
+ }
+ }
+ }
+ }
+
+ g = ConfigurationGroup('overgroup','Override Group')
+ self.g = g
+ config_register(StringValue(g, 's1'))
+ config_register(IntegerValue(g, 's2', default=10))
+ config_register(IntegerValue(g, 's3', default=10))
+ config_register(MultipleStringValue(g, 'choices'))
+
+ def tearDown(self):
+ djangosettings.LIVESETTINGS_OPTIONS = {}
+
+ def testOverriddenSetting(self):
+ """Accessing an overridden setting should give the override value."""
+ c = config_get('overgroup', 's2')
+ self.assertEquals(c.value, 100)
+
+ def testCantChangeSetting(self):
+ """When overridden, setting a value should not work, should get the overridden value"""
+ c = config_get('overgroup', 's2')
+ c.update(1)
+
+ c = config_get('overgroup', 's2')
+ self.assertEquals(c.value, 100)
+
+ def testNotOverriddenSetting(self):
+ """Settings which are not overridden should return their defaults"""
+ c = config_get('overgroup', 's3')
+
+ self.assertEquals(c.value, 10)
+
+ def testOverriddenListSetting(self):
+ """Make sure lists work when overridden"""
+
+ c = config_get('overgroup', 'choices')
+ v = c.value
+ self.assertEqual(len(v), 3)
+ self.assertEqual(v[0], "one")
+ self.assertEqual(v[1], "two")
+ self.assertEqual(v[2], "three")
diff --git a/livesettings/urls.py b/livesettings/urls.py
new file mode 100644
index 00000000..bc10b787
--- /dev/null
+++ b/livesettings/urls.py
@@ -0,0 +1,7 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('livesettings.views',
+ url(r'^$', 'site_settings', {}, name='satchmo_site_settings'),
+ url(r'^export/$', 'export_as_python', {}, name='settings_export'),
+ url(r'^(?P<group>[^/]+)/$', 'group_settings', name='group_settings'),
+)
diff --git a/livesettings/utils.py b/livesettings/utils.py
new file mode 100644
index 00000000..c0e0e293
--- /dev/null
+++ b/livesettings/utils.py
@@ -0,0 +1,87 @@
+import sys
+import types
+import os
+
+def can_loop_over(maybe):
+ """Test value to see if it is list like"""
+ try:
+ iter(maybe)
+ except:
+ return 0
+ else:
+ return 1
+
+def is_list_or_tuple(maybe):
+ return isinstance(maybe, (types.TupleType, types.ListType))
+
+
+def is_scalar(maybe):
+ """Test to see value is a string, an int, or some other scalar type"""
+ return is_string_like(maybe) or not can_loop_over(maybe)
+
+def is_string_like(maybe):
+ """Test value to see if it acts like a string"""
+ try:
+ maybe+""
+ except TypeError:
+ return 0
+ else:
+ return 1
+
+
+def flatten_list(sequence, scalarp=is_scalar, result=None):
+ """flatten out a list by putting sublist entries in the main list"""
+ if result is None:
+ result = []
+
+ for item in sequence:
+ if scalarp(item):
+ result.append(item)
+ else:
+ flatten_list(item, scalarp, result)
+
+def load_module(module):
+ """Load a named python module."""
+ try:
+ module = sys.modules[module]
+ except KeyError:
+ __import__(module)
+ module = sys.modules[module]
+ return module
+
+def get_flat_list(sequence):
+ """flatten out a list and return the flat list"""
+ flat = []
+ flatten_list(sequence, result=flat)
+ return flat
+
+def url_join(*args):
+ """Join any arbitrary strings into a forward-slash delimited string.
+ Do not strip leading / from first element, nor trailing / from last element.
+
+ This function can take lists as arguments, flattening them appropriately.
+
+ example:
+ url_join('one','two',['three','four'],'five') => 'one/two/three/four/five'
+ """
+ if len(args) == 0:
+ return ""
+
+ args = get_flat_list(args)
+
+ if len(args) == 1:
+ return str(args[0])
+
+ else:
+ args = [str(arg).replace("\\", "/") for arg in args]
+
+ work = [args[0]]
+ for arg in args[1:]:
+ if arg.startswith("/"):
+ work.append(arg[1:])
+ else:
+ work.append(arg)
+
+ joined = reduce(os.path.join, work)
+
+ return joined.replace("\\", "/")
diff --git a/livesettings/values.py b/livesettings/values.py
new file mode 100644
index 00000000..db952172
--- /dev/null
+++ b/livesettings/values.py
@@ -0,0 +1,628 @@
+"""Taken and modified from the dbsettings project.
+
+http://code.google.com/p/django-values/
+"""
+from decimal import Decimal
+from django import forms
+from django.core.exceptions import ImproperlyConfigured
+from django.utils import simplejson
+from django.utils.datastructures import SortedDict
+from django.utils.encoding import smart_str
+from django.utils.translation import gettext, ugettext_lazy as _
+from livesettings.models import find_setting, LongSetting, Setting, SettingNotSet
+from livesettings.overrides import get_overrides
+from livesettings.utils import load_module, is_string_like, is_list_or_tuple
+import datetime
+import logging
+import signals
+
+__all__ = ['BASE_GROUP', 'ConfigurationGroup', 'Value', 'BooleanValue', 'DecimalValue', 'DurationValue',
+ 'FloatValue', 'IntegerValue', 'ModuleValue', 'PercentValue', 'PositiveIntegerValue', 'SortedDotDict',
+ 'StringValue', 'LongStringValue', 'MultipleStringValue']
+
+_WARN = {}
+
+log = logging.getLogger('configuration')
+
+NOTSET = object()
+
+class SortedDotDict(SortedDict):
+
+ def __getattr__(self, key):
+ try:
+ return self[key]
+ except:
+ raise AttributeError, key
+
+ def __iter__(self):
+ vals = self.values()
+ for k in vals:
+ yield k
+
+ def values(self):
+ vals = super(SortedDotDict, self).values()
+ vals = [v for v in vals if isinstance(v, (ConfigurationGroup, Value))]
+ vals.sort()
+ return vals
+
+class ConfigurationGroup(SortedDotDict):
+ """A simple wrapper for a group of configuration values"""
+ def __init__(self, key, name, *args, **kwargs):
+ """Create a new ConfigurationGroup.
+
+ Arguments:
+ - key
+ - group name - for display to user
+
+ Named Arguments:
+ - ordering: integer, optional, defaults to 1.
+ - requires: See `Value` requires. The default `requires` all member values will have if not overridden.
+ - requiresvalue: See `Values` requires_value. The default `requires_value` if not overridden on the `Value` objects.
+ """
+ self.key = key
+ self.name = name
+ self.ordering = kwargs.pop('ordering', 1)
+ self.requires = kwargs.pop('requires', None)
+ if self.requires:
+ reqval = kwargs.pop('requiresvalue', key)
+ if not is_list_or_tuple(reqval):
+ reqval = (reqval, reqval)
+
+ self.requires_value = reqval[0]
+ self.requires.add_choice(reqval)
+
+ super(ConfigurationGroup, self).__init__(*args, **kwargs)
+
+ def __cmp__(self, other):
+ return cmp((self.ordering, self.name), (other.ordering, other.name))
+
+ def __eq__(self, other):
+ return (type(self) == type(other)
+ and self.ordering == other.ordering
+ and self.name == other.name)
+
+ def __ne__(self, other):
+ return not self == other
+
+ def dict_values(self, load_modules=True):
+ vals = {}
+ keys = super(ConfigurationGroup, self).keys()
+ for key in keys:
+ v = self[key]
+ if isinstance(v, Value):
+ value = v.value
+ else:
+ value = v
+ vals[key] = value
+ return vals
+
+ def values(self):
+ vals = super(ConfigurationGroup, self).values()
+ return [v for v in vals if v.enabled()]
+
+BASE_GROUP = ConfigurationGroup('BASE', _('Base Settings'), ordering=0)
+
+class Value(object):
+
+ creation_counter = 0
+
+ def __init__(self, group, key, **kwargs):
+ """
+ Create a new Value object for configuration.
+
+ Args:
+ - `ConfigurationGroup`
+ - key - a string key
+
+ Named arguments:
+ - `description` - Will be passed to the field for form usage. Should be a translation proxy. Ex: _('example')
+ - `help_text` - Will be passed to the field for form usage.
+ - `choices` - If given, then the form field will use a select box
+ - `ordering` - Defaults to alphabetical by key if not given.
+ - `requires` - If given as a `Value`, then this field will only be rendered if that Value evaluates true (for Boolean requires) or the proper key is in the associated value.
+ - `requiresvalue` - If set, then this field will only be rendered if that value is in the list returned by self.value. Defaults to self.key.
+ - `hidden` - If true, then render a hidden field.
+ - `default` - If given, then this Value will return that default whenever it has no assocated `Setting`.
+ - `update_callback` - if given, then this value will call the callback whenever updated
+ """
+ self.group = group
+ self.key = key
+ self.description = kwargs.get('description', None)
+ self.help_text = kwargs.get('help_text')
+ self.choices = kwargs.get('choices',[])
+ self.ordering = kwargs.pop('ordering', 0)
+ self.hidden = kwargs.pop('hidden', False)
+ self.update_callback = kwargs.pop('update_callback', None)
+ self.requires = kwargs.pop('requires', None)
+ if self.requires:
+ reqval = kwargs.pop('requiresvalue', key)
+ if not is_list_or_tuple(reqval):
+ reqval = (reqval, reqval)
+
+ self.requires_value = reqval[0]
+ self.requires.add_choice(reqval)
+
+ elif group.requires:
+ self.requires = group.requires
+ self.requires_value = group.requires_value
+
+ if kwargs.has_key('default'):
+ self.default = kwargs.pop('default')
+ self.use_default = True
+ else:
+ self.use_default = False
+
+ self.creation_counter = Value.creation_counter
+ Value.creation_counter += 1
+
+ def __cmp__(self, other):
+ return cmp((self.ordering, self.description, self.creation_counter), (other.ordering, other.description, other.creation_counter))
+
+ def __eq__(self, other):
+ if type(self) == type(other):
+ return self.value == other.value
+ else:
+ return self.value == other
+
+ def __iter__(self):
+ return iter(self.value)
+
+ def __unicode__(self):
+ return unicode(self.value)
+
+ def __str__(self):
+ return str(self.value)
+
+ def add_choice(self, choice):
+ """Add a choice if it doesn't already exist."""
+ if not is_list_or_tuple(choice):
+ choice = (choice, choice)
+ skip = False
+ for k, v in self.choices:
+ if k == choice[0]:
+ skip = True
+ break
+ if not skip:
+ self.choices += (choice, )
+
+ def choice_field(self, **kwargs):
+ if self.hidden:
+ kwargs['widget'] = forms.MultipleHiddenInput()
+ return forms.ChoiceField(choices=self.choices, **kwargs)
+
+ def _choice_values(self):
+ choices = self.choices
+ vals = self.value
+ return [x for x in choices if x[0] in vals]
+
+ choice_values = property(fget=_choice_values)
+
+ def copy(self):
+ new_value = self.__class__(self.key)
+ new_value.__dict__ = self.__dict__.copy()
+ return new_value
+
+ def _default_text(self):
+ if not self.use_default:
+ note = ""
+ else:
+ if self.default == "":
+ note = _('Default value: ""')
+
+ elif self.choices:
+ work = []
+ for x in self.choices:
+ if x[0] in self.default:
+ work.append(smart_str(x[1]))
+ note = gettext('Default value: ') + ", ".join(work)
+
+ else:
+ note = _("Default value: %s") % unicode(self.default)
+
+ return note
+
+ default_text = property(fget=_default_text)
+
+ def enabled(self):
+ enabled = False
+ try:
+ if not self.requires:
+ enabled = True
+ else:
+ v = self.requires.value
+ if self.requires.choices:
+ enabled = self.requires_value == v or self.requires_value in v
+ elif v:
+ enabled = True
+ except SettingNotSet:
+ pass
+ return enabled
+
+ def make_field(self, **kwargs):
+ if self.choices:
+ if self.hidden:
+ kwargs['widget'] = forms.MultipleHiddenInput()
+ field = self.choice_field(**kwargs)
+ else:
+ if self.hidden:
+ kwargs['widget'] = forms.HiddenInput()
+ field = self.field(**kwargs)
+
+ field.group = self.group
+ field.default_text = self.default_text
+ return field
+
+ def make_setting(self, db_value):
+ log.debug('new setting %s.%s', self.group.key, self.key)
+ return Setting(group=self.group.key, key=self.key, value=db_value)
+
+ def _setting(self):
+ return find_setting(self.group.key, self.key)
+
+ setting = property(fget = _setting)
+
+ def _value(self):
+ use_db, overrides = get_overrides()
+
+ if not use_db:
+ try:
+ val = overrides[self.group.key][self.key]
+ except KeyError:
+ if self.use_default:
+ val = self.default
+ else:
+ raise SettingNotSet('%s.%s is not in your LIVESETTINGS_OPTIONS' % (self.group.key, self.key))
+
+ else:
+ try:
+ val = self.setting.value
+
+ except SettingNotSet, sns:
+ if self.use_default:
+ val = self.default
+ if overrides:
+ # maybe override the default
+ grp = overrides.get(self.group.key, {})
+ if grp.has_key(self.key):
+ val = grp[self.key]
+ else:
+ val = NOTSET
+
+ except AttributeError, ae:
+ log.error("Attribute error: %s", ae)
+ log.error("%s: Could not get _value of %s", self.key, self.setting)
+ raise(ae)
+
+ except Exception, e:
+ global _WARN
+ log.error(e)
+ if str(e).find("configuration_setting") > -1:
+ if not _WARN.has_key('configuration_setting'):
+ log.warn('Error loading setting %s.%s from table, OK if you are in syncdb', self.group.key, self.key)
+ _WARN['configuration_setting'] = True
+
+ if self.use_default:
+ val = self.default
+ else:
+ raise ImproperlyConfigured("All settings used in startup must have defaults, %s.%s does not", self.group.key, self.key)
+ else:
+ import traceback
+ traceback.print_exc()
+ log.warn("Problem finding settings %s.%s, %s", self.group.key, self.key, e)
+ raise SettingNotSet("Startup error, couldn't load %s.%s" %(self.group.key, self.key))
+ return val
+
+ def update(self, value):
+ use_db, overrides = get_overrides()
+
+ if use_db:
+ current_value = self.value
+
+ new_value = self.to_python(value)
+ if current_value != new_value:
+ if self.update_callback:
+ new_value = apply(self.update_callback, (current_value, new_value))
+
+ db_value = self.get_db_prep_save(new_value)
+
+ try:
+ s = self.setting
+ s.value = db_value
+
+ except SettingNotSet:
+ s = self.make_setting(db_value)
+
+ if self.use_default and self.default == new_value:
+ if s.id:
+ log.info("Deleted setting %s.%s", self.group.key, self.key)
+ s.delete()
+ else:
+ log.info("Updated setting %s.%s = %s", self.group.key, self.key, value)
+ s.save()
+
+ signals.configuration_value_changed.send(self, old_value=current_value, new_value=new_value, setting=self)
+
+ return True
+ else:
+ log.debug('not updating setting %s.%s - livesettings db is disabled',self.group.key, self.key)
+
+ return False
+
+ @property
+ def value(self):
+ val = self._value()
+ return self.to_python(val)
+
+ @property
+ def editor_value(self):
+ val = self._value()
+ return self.to_editor(val)
+
+ # Subclasses should override the following methods where applicable
+
+ def to_python(self, value):
+ "Returns a native Python object suitable for immediate use"
+ if value == NOTSET:
+ value = None
+ return value
+
+ def get_db_prep_save(self, value):
+ "Returns a value suitable for storage into a CharField"
+ if value == NOTSET:
+ value = ""
+ return unicode(value)
+
+ def to_editor(self, value):
+ "Returns a value suitable for display in a form widget"
+ if value == NOTSET:
+ return NOTSET
+ return unicode(value)
+
+###############
+# VALUE TYPES #
+###############
+
+class BooleanValue(Value):
+
+ class field(forms.BooleanField):
+
+ def __init__(self, *args, **kwargs):
+ kwargs['required'] = False
+ forms.BooleanField.__init__(self, *args, **kwargs)
+
+ def add_choice(self, choice):
+ # ignore choice adding for boolean types
+ pass
+
+ def to_python(self, value):
+ if value in (True, 't', 'True', 1, '1'):
+ return True
+ return False
+
+ to_editor = to_python
+
+class DecimalValue(Value):
+ class field(forms.DecimalField):
+
+ def __init__(self, *args, **kwargs):
+ kwargs['required'] = False
+ forms.DecimalField.__init__(self, *args, **kwargs)
+
+ def to_python(self, value):
+ if value==NOTSET:
+ return Decimal("0")
+
+ try:
+ return Decimal(value)
+ except TypeError, te:
+ log.warning("Can't convert %s to Decimal for settings %s.%s", value, self.group.key, self.key)
+ raise TypeError(te)
+
+ def to_editor(self, value):
+ if value == NOTSET:
+ return "0"
+ else:
+ return unicode(value)
+
+# DurationValue has a lot of duplication and ugliness because of issue #2443
+# Until DurationField is sorted out, this has to do some extra work
+class DurationValue(Value):
+
+ class field(forms.CharField):
+ def clean(self, value):
+ try:
+ return datetime.timedelta(seconds=float(value))
+ except (ValueError, TypeError):
+ raise forms.ValidationError('This value must be a real number.')
+ except OverflowError:
+ raise forms.ValidationError('The maximum allowed value is %s' % datetime.timedelta.max)
+
+ def to_python(self, value):
+ if value == NOTSET:
+ value = 0
+ if isinstance(value, datetime.timedelta):
+ return value
+ try:
+ return datetime.timedelta(seconds=float(value))
+ except (ValueError, TypeError):
+ raise forms.ValidationError('This value must be a real number.')
+ except OverflowError:
+ raise forms.ValidationError('The maximum allowed value is %s' % datetime.timedelta.max)
+
+ def get_db_prep_save(self, value):
+ if value == NOTSET:
+ return NOTSET
+ else:
+ return unicode(value.days * 24 * 3600 + value.seconds + float(value.microseconds) / 1000000)
+
+class FloatValue(Value):
+
+ class field(forms.FloatField):
+
+ def __init__(self, *args, **kwargs):
+ kwargs['required'] = False
+ forms.FloatField.__init__(self, *args, **kwargs)
+
+ def to_python(self, value):
+ if value == NOTSET:
+ value = 0
+ return float(value)
+
+ def to_editor(self, value):
+ if value == NOTSET:
+ return "0"
+ else:
+ return unicode(value)
+
+class IntegerValue(Value):
+ class field(forms.IntegerField):
+
+ def __init__(self, *args, **kwargs):
+ kwargs['required'] = False
+ forms.IntegerField.__init__(self, *args, **kwargs)
+
+ def to_python(self, value):
+ if value == NOTSET:
+ value = 0
+ return int(value)
+
+ def to_editor(self, value):
+ if value == NOTSET:
+ return "0"
+ else:
+ return unicode(value)
+
+
+class PercentValue(Value):
+
+ class field(forms.DecimalField):
+
+ def __init__(self, *args, **kwargs):
+ kwargs['required'] = False
+ forms.DecimalField.__init__(self, 100, 0, 5, 2, *args, **kwargs)
+
+ class widget(forms.TextInput):
+ def render(self, *args, **kwargs):
+ # Place a percent sign after a smaller text field
+ attrs = kwargs.pop('attrs', {})
+ attrs['size'] = attrs['max_length'] = 6
+ return forms.TextInput.render(self, attrs=attrs, *args, **kwargs) + '%'
+
+ def to_python(self, value):
+ if value == NOTSET:
+ value = 0
+ return Decimal(value) / 100
+
+ def to_editor(self, value):
+ if value == NOTSET:
+ return "0"
+ else:
+ return unicode(value)
+
+class PositiveIntegerValue(IntegerValue):
+
+ class field(forms.IntegerField):
+
+ def __init__(self, *args, **kwargs):
+ kwargs['min_value'] = 0
+ forms.IntegerField.__init__(self, *args, **kwargs)
+
+
+class StringValue(Value):
+
+ class field(forms.CharField):
+ def __init__(self, *args, **kwargs):
+ kwargs['required'] = False
+ forms.CharField.__init__(self, *args, **kwargs)
+
+ def to_python(self, value):
+ if value == NOTSET:
+ value = ""
+ return unicode(value)
+
+ to_editor = to_python
+
+class LongStringValue(Value):
+
+ class field(forms.CharField):
+ def __init__(self, *args, **kwargs):
+ kwargs['required'] = False
+ kwargs['widget'] = forms.Textarea()
+ forms.CharField.__init__(self, *args, **kwargs)
+
+ def make_setting(self, db_value):
+ log.debug('new long setting %s.%s', self.group.key, self.key)
+ return LongSetting(group=self.group.key, key=self.key, value=db_value)
+
+ def to_python(self, value):
+ if value == NOTSET:
+ value = ""
+ return unicode(value)
+
+ to_editor = to_python
+
+
+class MultipleStringValue(Value):
+
+ class field(forms.CharField):
+
+ def __init__(self, *args, **kwargs):
+ kwargs['required'] = False
+ forms.CharField.__init__(self, *args, **kwargs)
+
+ def choice_field(self, **kwargs):
+ kwargs['required'] = False
+ return forms.MultipleChoiceField(choices=self.choices, **kwargs)
+
+ def get_db_prep_save(self, value):
+ if is_string_like(value):
+ value = [value]
+ return simplejson.dumps(value)
+
+ def to_python(self, value):
+ if not value or value == NOTSET:
+ return []
+ if is_list_or_tuple(value):
+ return value
+ else:
+ try:
+ return simplejson.loads(value)
+ except:
+ if is_string_like(value):
+ return [value]
+ else:
+ log.warning('Could not decode returning empty list: %s', value)
+ return []
+
+
+ to_editor = to_python
+
+class ModuleValue(Value):
+ """Handles setting modules, storing them as strings in the db."""
+
+ class field(forms.CharField):
+
+ def __init__(self, *args, **kwargs):
+ kwargs['required'] = False
+ forms.CharField.__init__(self, *args, **kwargs)
+
+ def load_module(self, module):
+ """Load a child module"""
+ value = self._value()
+ if value == NOTSET:
+ raise SettingNotSet("%s.%s", self.group.key, self.key)
+ else:
+ return load_module("%s.%s" % (value, module))
+
+ def to_python(self, value):
+ if value == NOTSET:
+ v = {}
+ else:
+ v = load_module(value)
+ return v
+
+ def to_editor(self, value):
+ if value == NOTSET:
+ value = ""
+ return value
+
diff --git a/livesettings/views.py b/livesettings/views.py
new file mode 100644
index 00000000..c2a2ccdc
--- /dev/null
+++ b/livesettings/views.py
@@ -0,0 +1,93 @@
+from django.http import HttpResponseRedirect
+from django.core.urlresolvers import reverse
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.contrib.admin.views.decorators import staff_member_required
+from django.views.decorators.cache import never_cache
+from livesettings import ConfigurationSettings, forms
+from livesettings.overrides import get_overrides
+import logging
+
+log = logging.getLogger('configuration.views')
+
+def group_settings(request, group, template='livesettings/group_settings.html'):
+ # Determine what set of settings this editor is used for
+
+ use_db, overrides = get_overrides();
+
+ mgr = ConfigurationSettings()
+
+ settings = mgr[group]
+ title = settings.name
+ log.debug('title: %s', title)
+
+ if use_db:
+ # Create an editor customized for the current user
+ #editor = forms.customized_editor(settings)
+
+ if request.method == 'POST':
+ # Populate the form with user-submitted data
+ data = request.POST.copy()
+ form = forms.SettingsEditor(data, settings=settings)
+ if form.is_valid():
+ form.full_clean()
+ for name, value in form.cleaned_data.items():
+ group, key = name.split('__')
+ cfg = mgr.get_config(group, key)
+ if cfg.update(value):
+
+ # Give user feedback as to which settings were changed
+ request.user.message_set.create(message='Updated %s on %s' % (cfg.key, cfg.group.key))
+
+ return HttpResponseRedirect(request.path)
+ else:
+ # Leave the form populated with current setting values
+ #form = editor()
+ form = forms.SettingsEditor(settings=settings)
+ else:
+ form = None
+
+ return render_to_response(template, {
+ 'all_groups': mgr.groups(),
+ 'title': title,
+ 'group' : settings,
+ 'form': form,
+ 'use_db' : use_db
+ }, context_instance=RequestContext(request))
+group_settings = never_cache(staff_member_required(group_settings))
+
+# Site-wide setting editor is identical, but without a group
+# staff_member_required is implied, since it calls group_settings
+def site_settings(request):
+ mgr = ConfigurationSettings()
+ default_group= mgr.groups()[0].key
+ return HttpResponseRedirect(reverse('group_settings', args=[default_group]))
+ #return group_settings(request, group=None, template='livesettings/site_settings.html')
+
+def export_as_python(request):
+ """Export site settings as a dictionary of dictionaries"""
+
+ from livesettings.models import Setting, LongSetting
+ import pprint
+
+ work = {}
+ both = list(Setting.objects.all())
+ both.extend(list(LongSetting.objects.all()))
+
+ for s in both:
+ if not work.has_key(s.site.id):
+ work[s.site.id] = {}
+ sitesettings = work[s.site.id]
+
+ if not sitesettings.has_key(s.group):
+ sitesettings[s.group] = {}
+ sitegroup = sitesettings[s.group]
+
+ sitegroup[s.key] = s.value
+
+ pp = pprint.PrettyPrinter(indent=4)
+ pretty = pp.pformat(work)
+
+ return render_to_response('livesettings/text.txt', { 'text' : pretty }, mimetype='text/plain')
+
+export_as_python = never_cache(staff_member_required(export_as_python))
diff --git a/settings.py b/settings.py
index 2acc920e..98555997 100644
--- a/settings.py
+++ b/settings.py
@@ -11,8 +11,10 @@ SECRET_KEY = '$oo^&_m&qwbib=(_4m_n*zn-d=g#s0he5fx9xonnym#8p6yigm'
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.load_template_source',
'django.template.loaders.app_directories.load_template_source',
+
+ #below is forum stuff for this tuple
'forum.modules.module_templates_loader',#todo: remove this
- 'forum.skins.load_template_source',
+ 'forum.skins.load_template_source',#forum stuff
# 'django.template.loaders.eggs.load_template_source',
)
@@ -25,6 +27,8 @@ MIDDLEWARE_CLASSES = (
#'django.middleware.cache.FetchFromCacheMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
#'django.middleware.sqlprint.SqlPrintingMiddleware',
+
+ #below is forum stuff for this tuple
'forum.middleware.anon_user.ConnectToSessionMessagesMiddleware',
'forum.middleware.pagesize.QuestionsPageSizeMiddleware',
'forum.middleware.cancel.CancelActionMiddleware',
@@ -34,6 +38,7 @@ MIDDLEWARE_CLASSES = (
'forum.middleware.view_log.ViewLogMiddleware',
)
+#all of these are necessary for the forum and absend in default settings.py
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.request',
'forum.context.application_settings',
@@ -45,6 +50,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
ROOT_URLCONF = 'urls'
TEMPLATE_DIRS = (
+ #specific to forum
os.path.join(os.path.dirname(__file__),'forum','skins').replace('\\','/'),
)
@@ -66,6 +72,8 @@ INSTALLED_APPS = (
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
+
+ #all of these are needed for the forum
'django.contrib.admin',
'django.contrib.humanize',
'django.contrib.sitemaps',
@@ -75,14 +83,13 @@ INSTALLED_APPS = (
'debug_toolbar' ,
#'forum.importers.stackexchange', #se loader
'south',
+ 'livesettings',
+ 'keyedcache',
)
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',)
-if USE_FB_CONNECT:
- INSTALLED_APPS += ('fbconnect',)
-
-#load optional plugin module for external password login
+#this needs to go
if 'USE_EXTERNAL_LEGACY_LOGIN' in locals() and USE_EXTERNAL_LEGACY_LOGIN:
INSTALLED_APPS += (EXTERNAL_LEGACY_LOGIN_MODULE,)
diff --git a/settings_local.py.dist b/settings_local.py.dist
index 137ad6ec..3ae8b512 100755
--- a/settings_local.py.dist
+++ b/settings_local.py.dist
@@ -1,6 +1,5 @@
# encoding:utf-8
import os.path
-from django.utils.translation import ugettext as _
SITE_SRC_ROOT = os.path.dirname(__file__)
LOG_FILENAME = 'askbot.log'
@@ -18,26 +17,17 @@ ADMINS = (('Forum Admin', 'forum@example.com'),)
MANAGERS = ADMINS
#DEBUG SETTINGS
-DEBUG = False
+DEBUG = True
TEMPLATE_DEBUG = DEBUG
INTERNAL_IPS = ('127.0.0.1',)
DATABASE_NAME = '' # Or path to database file if using sqlite3.
DATABASE_USER = '' # Not used with sqlite3.
DATABASE_PASSWORD = '' # Not used with sqlite3.
-DATABASE_ENGINE = '' #mysql, etc
+DATABASE_ENGINE = 'mysql' #mysql, etc
DATABASE_HOST = ''
DATABASE_PORT = ''
-#set this value to 'dummy://' if you don't want to use cache, or set up your favourite caching mechanism
-#see http://docs.djangoproject.com/en/1.1/topics/cache/ for details
-#example (set local file system cache in a cache folder in the root of the askbot install):
-#CACHE_BACKEND = 'file://%s' % os.path.join(os.path.dirname(__file__),'cache').replace('\\','/')
-CACHE_BACKEND = 'dummy://'
-
-#If you use memcache you may want to uncomment the following line to enable memcached based sessions
-#SESSION_ENGINE = 'django.contrib.sessions.backends.cache_db'
-
#email server settings
SERVER_EMAIL = ''
DEFAULT_FROM_EMAIL = ''
@@ -48,80 +38,32 @@ EMAIL_HOST='askbot.org'
EMAIL_PORT='25'
EMAIL_USE_TLS=False
-#HACK - anonymous user email - for email-less users
-ANONYMOUS_USER_EMAIL = 'anonymous@askbot.org'
-
#LOCALIZATIONS
TIME_ZONE = 'America/New_York'
-###########################
-#
-# this will allow running your forum with url like http://site.com/forum
-#
-# FORUM_SCRIPT_ALIAS = 'forum/'
-#
+#this will allow running your forum with url like http://site.com/forum
+#FORUM_SCRIPT_ALIAS = 'forum/'
FORUM_SCRIPT_ALIAS = '' #no leading slash, default = '' empty string
-
#OTHER SETTINGS
-APP_TITLE = u'ASKBOT: Open Source Q&A Forum'
-APP_SHORT_NAME = u'ASKBOT'
-APP_KEYWORDS = u'ASKBOT,CNPROG,forum,community'
-APP_DESCRIPTION = u'Ask and answer questions.'
-APP_INTRO = u'<p>Ask and answer questions, make the world better!</p>'
-APP_COPYRIGHT = 'Copyright ASKBOT, 2009. Some rights reserved under creative commons license.'
-LOGIN_URL = '/%s%s%s' % (FORUM_SCRIPT_ALIAS,'account/','signin/')
-GREETING_URL = LOGIN_URL #may be url of "faq" page or "about", etc
+_ = lambda v:v
+LOGIN_URL = '/%s%s%s' % (FORUM_SCRIPT_ALIAS,_('account/'),_('signin/'))
USE_I18N = True
LANGUAGE_CODE = 'en'
-EMAIL_VALIDATION = 'off' #string - on|off
-MIN_USERNAME_LENGTH = 1
-EMAIL_UNIQUE = False
-APP_URL = 'http://askbot.org' #used by email notif system and RSS
-GOOGLE_SITEMAP_CODE = ''
-GOOGLE_ANALYTICS_KEY = ''
-WIKI_ON = True
-FEEDBACK_SITE_URL = None #None or url
-EDITABLE_SCREEN_NAME = False #True or False - can user change screen name?
-
-DJANGO_VERSION = 1.1
-RESOURCE_REVISION=4
-
-#please get these at recaptcha.net
-RECAPTCHA_PRIVATE_KEY='...'
-RECAPTCHA_PUBLIC_KEY='...'
-ASKBOT_DEFAULT_SKIN = 'default'
-
-
-#Facebook settings
-USE_FB_CONNECT=False
-FB_API_KEY='' #your api key from facebook
-FB_SECRET='' #your application secret
-USE_EXTERNAL_LEGACY_LOGIN = False #DO NOT USE, and do not delete this line, will be removed later
-#counter colors
-from forum_modules.grapefruit import Color
-VOTE_COUNTER_EXPECTED_MAXIMUM = 5
-COLORS_VOTE_COUNTER_EMPTY_BG = 'white'
-COLORS_VOTE_COUNTER_EMPTY_FG = 'gray'
-COLORS_VOTE_COUNTER_MIN_BG = 'white'
-COLORS_VOTE_COUNTER_MIN_FG = 'black'
-COLORS_VOTE_COUNTER_MAX_BG = '#a9d0f5'
-COLORS_VOTE_COUNTER_MAX_FG = Color.NewFromHtml(COLORS_VOTE_COUNTER_MAX_BG).DarkerColor(0.7).html
-VIEW_COUNTER_EXPECTED_MAXIMUM = 100
-COLORS_VIEW_COUNTER_EMPTY_BG = 'gray'
-COLORS_VIEW_COUNTER_EMPTY_FG = 'white'
-COLORS_VIEW_COUNTER_MIN_BG = '#D0F5A9'
-COLORS_VIEW_COUNTER_MIN_FG = Color.NewFromHtml(COLORS_VIEW_COUNTER_MIN_BG).DarkerColor(0.6).html
-COLORS_VIEW_COUNTER_MAX_BG = '#FF8000'#'#F7BE81'
-COLORS_VIEW_COUNTER_MAX_FG = Color.NewFromHtml(COLORS_VIEW_COUNTER_MAX_BG).DarkerColor(0.7).html
-ANSWER_COUNTER_EXPECTED_MAXIMUM = 4
-COLORS_ANSWER_COUNTER_EMPTY_BG = Color.NewFromHtml('#a40000').Blend(Color.NewFromHtml('white'),0.8).html
-COLORS_ANSWER_COUNTER_EMPTY_FG = 'yellow'
-COLORS_ANSWER_COUNTER_MIN_BG = '#AEB404'#'#81F7F3'#'#A9D0F5'#'#045FB4'
-COLORS_ANSWER_COUNTER_MIN_FG = 'white'#'#81F7F3'
-COLORS_ANSWER_COUNTER_MAX_BG = Color.NewFromHtml('#61380B').Blend(Color.NewFromHtml('white'),0.75).html
-COLORS_ANSWER_COUNTER_MAX_FG = '#ffff00'
-COLORS_ANSWER_COUNTER_ACCEPTED_BG = Color.NewFromHtml('darkgreen').Blend(Color.NewFromHtml('white'),0.8).html
-COLORS_ANSWER_COUNTER_ACCEPTED_FG = '#D0F5A9'
+#Do not use these - will be phased out
+USE_EXTERNAL_LEGACY_LOGIN = False
+EXTERNAL_LEGACY_LOGIN_HOST = 'login.askbot.org'
+EXTERNAL_LEGACY_LOGIN_PORT = 80
+EXTERNAL_LEGACY_LOGIN_PROVIDER_NAME = '<span class="orange">ASKBOT</span>'
+
+#SPHINX SETTINGS
+USE_SPHINX_SEARCH = False #if True all SPHINX_* settings are required
+#also sphinx search engine and djangosphinxs app must be installed
+#sample sphinx configuration file is /sphinx/sphinx.conf
+SPHINX_API_VERSION = 0x113 #refer to djangosphinx documentation
+SPHINX_SEARCH_INDICES=('askbot',) #a tuple of index names remember about a comma after the
+#last item, especially if you have just one :)
+SPHINX_SERVER='localhost'
+SPHINX_PORT=3312
diff --git a/urls.py b/urls.py
index 339791c9..8c349456 100644
--- a/urls.py
+++ b/urls.py
@@ -1,5 +1,7 @@
-from django.conf.urls.defaults import *
-from django.utils.translation import ugettext as _
+"""
+main url configuration file for the askbot site
+"""
+from django.conf.urls.defaults import patterns, include, url
from django.conf import settings
from django.contrib import admin
@@ -8,6 +10,7 @@ admin.autodiscover()
urlpatterns = patterns('',
(r'^%s' % settings.FORUM_SCRIPT_ALIAS, include('forum.urls')),
(r'^admin/', include(admin.site.urls)),
+ (r'^settings/', include('livesettings.urls')),
)
if 'rosetta' in settings.INSTALLED_APPS: