summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEvgeny Fadeev <evgeny.fadeev@gmail.com>2010-04-27 19:42:33 -0400
committerEvgeny Fadeev <evgeny.fadeev@gmail.com>2010-04-27 19:42:33 -0400
commit8130338044d635ce9038d7dd58af06f59cc330bc (patch)
tree25c74fe61ab9279f04746370cdd21a7436cf4568
parente48efec236c3aaae4e669427ef7f6c614d12fe0c (diff)
downloadaskbot-8130338044d635ce9038d7dd58af06f59cc330bc.tar.gz
askbot-8130338044d635ce9038d7dd58af06f59cc330bc.tar.bz2
askbot-8130338044d635ce9038d7dd58af06f59cc330bc.zip
started adding admin interface
-rw-r--r--[-rwxr-xr-x]django_authopenid/urls.py0
-rw-r--r--[-rwxr-xr-x]django_authopenid/views.py0
-rw-r--r--[-rwxr-xr-x]fbconnect/__init__.py0
-rw-r--r--[-rwxr-xr-x]fbconnect/fb.py0
-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.py0
-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.py0
-rw-r--r--[-rwxr-xr-x]forum/auth.py189
-rw-r--r--[-rwxr-xr-x]forum/badges/__init__.py0
-rw-r--r--[-rwxr-xr-x]forum/badges/base.py0
-rw-r--r--[-rwxr-xr-x]forum/const.py0
-rw-r--r--forum/documentation/scratch5
-rw-r--r--[-rwxr-xr-x]forum/feed.py0
-rw-r--r--[-rwxr-xr-x]forum/forms.py0
-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.py0
-rw-r--r--[-rwxr-xr-x]forum/management/commands/subscribe_everyone.py0
-rwxr-xr-xforum/management/commands/sximport.py109
-rw-r--r--[-rwxr-xr-x]forum/middleware/__init__.py0
-rw-r--r--[-rwxr-xr-x]forum/middleware/anon_user.py0
-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__.py0
-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.py0
-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--[-rwxr-xr-x]forum/settings.py3
-rw-r--r--[-rwxr-xr-x]forum/sitemap.py0
-rw-r--r--[-rwxr-xr-x]forum/skins/__init__.py0
-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.py0
-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.py0
-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.py10
-rw-r--r--[-rwxr-xr-x]forum/views/meta.py0
-rw-r--r--[-rwxr-xr-x]forum/views/users.py2
-rw-r--r--[-rwxr-xr-x]forum/views/writers.py0
-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.html56
-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.py89
-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.py91
-rw-r--r--settings.py15
-rw-r--r--urls.py2
155 files changed, 4739 insertions, 187 deletions
diff --git a/django_authopenid/urls.py b/django_authopenid/urls.py
index e1986d19..e1986d19 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..4f7d3efa 100755..100644
--- a/django_authopenid/views.py
+++ b/django_authopenid/views.py
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..afcd8210 100755..100644
--- a/fbconnect/fb.py
+++ b/fbconnect/fb.py
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..bf2d4364 100755..100644
--- a/fbconnect/urls.py
+++ b/fbconnect/urls.py
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..3afa2241 100755..100644
--- a/forum/admin.py
+++ b/forum/admin.py
diff --git a/forum/auth.py b/forum/auth.py
index 5d6e71c4..5bc88297 100755..100644
--- a/forum/auth.py
+++ b/forum/auth.py
@@ -14,30 +14,87 @@ 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
+from livesettings import ConfigurationGroup, IntegerValue, config_register
+MINIMUM_REPUTATION_SETTINGS = ConfigurationGroup(
+ 'MIN_REP',
+ _('Minimum reputation settings'),
+ ordering=0)
+
+MIN_REP_DATA = (
+ #(key, min_rep, description),
+ ('VOTE_UP', 15, _('Vote')),
+ ('FLAG_OFFENSIVE', 15, _('Flag offensive')),
+ #('POST_IMAGES', 15, _('Upload images')),#todo: looks like unused
+ ('LEAVE_COMMENTS', 50, _('Leave comments')),
+ ('UPLOAD_FILES', 60, _('Upload files')),
+ ('VOTE_DOWN', 100, _('Downvote')),
+ ('CLOSE_OWN_QUESTIONS', 250, _('Close own questions')),
+ ('RETAG_OTHER_QUESTIONS', 500, _('Retag questions posted by other people')),
+ ('REOPEN_OWN_QUESTIONS', 500, _('Reopen own questions')),
+ ('EDIT_COMMUNITY_WIKI_POSTS', 750, _('Edit community wiki posts')),
+ ('EDIT_OTHER_POSTS', 2000, _('Edit posts authored by other people')),
+ ('DELETE_COMMENTS', 2000, _('Delete comments')),
+ ('VIEW_OFFENSIVE_FLAGS', 2000, _('View offensive flags')),
+ ('DISABLE_URL_NOFOLLOW', 2000, _('Disable url nofollow')),
+ ('CLOSE_OTHER_QUESTIONS', 3000, _('Close questions asked by others')),
+ ('LOCK_POSTS', 4000, _('Lock posts')),
+)
+
+#rolled into eval block to save on typing an debugging
+python_format_string = '%(VAR_NAME)s = config_register(IntegerValue(%(SETTINGS_GROUP_KEY)s,\'%(VAR_NAME)s\', default = %(DEFAULT_VALUE)d,description = u\'%(VAR_DESCRIPTION)s\', ordering=%(ORDERING)d))\n'
+i = 0
+for item in MIN_REP_DATA:
+ name = item[0]
+ value = item[1]
+ description = item[2]
+ python_string = python_format_string \
+ % {
+ 'SETTINGS_GROUP_KEY':'MINIMUM_REPUTATION_SETTINGS',
+ 'VAR_NAME':name,
+ 'DEFAULT_VALUE':value,
+ 'VAR_DESCRIPTION':description,
+ 'ORDERING':i,
+ }
+ i += 1
+ exec(python_string)
+
+VOTE_RULES_DATA = (
+ ('scope_votes_per_user_per_day', 30, _('Maximum votes per day')), # how many votes of one user has everyday
+ ('scope_flags_per_user_per_day', 5, _('Maximum flags per day')), # how many times user can flag posts everyday
+ ('scope_warn_votes_left', 5, _('How early to start warning about the vote per day limit')), # start when to warn user how many votes left
+ ('scope_deny_unvote_days', 1, _('Days to allow canceling votes')), # if 1 days passed, user can't cancel votes.
+ ('scope_flags_invisible_main_page', 3, _('Number of flags to hide post')), # post doesn't show on main page if has more than 3 offensive flags
+ ('scope_flags_delete_post', 5, _('Number of flags to delete post')),# post will be deleted if it has more than 5 offensive flags
+)
+
+VOTE_RULE_SETTINGS = ConfigurationGroup(
+ 'VOTE_RULES',
+ _('Vote and flag rules'),
+ ordering=0)
+
+i = 0
+for item in VOTE_RULES_DATA:
+ name = item[0]
+ value = item[1]
+ description = item[2]
+ python_string = python_format_string \
+ % {
+ 'SETTINGS_GROUP_KEY':'VOTE_RULE_SETTINGS',
+ 'VAR_NAME':name,
+ 'DEFAULT_VALUE':value,
+ 'VAR_DESCRIPTION':description,
+ 'ORDERING':i,
+ }
+ i += 1
+ exec(python_string)
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
+ 'scope_votes_per_user_per_day' : scope_votes_per_user_per_day, # how many votes of one user has everyday
+ 'scope_flags_per_user_per_day' : scope_flags_per_user_per_day, # how many times user can flag posts everyday
+ 'scope_warn_votes_left' : scope_warn_votes_left, # start when to warn user how many votes left
+ 'scope_deny_unvote_days' : scope_deny_unvote_days, # if 1 days passed, user can't cancel votes.
+ 'scope_flags_invisible_main_page' : scope_flags_invisible_main_page, # post doesn't show on main page if has more than 3 offensive flags
+ 'scope_flags_delete_post' : scope_flags_delete_post, # post will be deleted if it has more than 5 offensive flags
}
REPUTATION_RULES = {
@@ -64,13 +121,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 >= VOTE_UP.value 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 >= FLAG_OFFENSIVE.value or
user.is_superuser)
def can_add_comments(user,subject):
@@ -78,7 +135,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 >= LEAVE_COMMENTS.value:
return True
if user.is_superuser:
return True
@@ -89,53 +146,53 @@ 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 >= VOTE_DOWN.value 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
+ RETAG_OTHER_QUESTIONS.value <= user.reputation < EDIT_OTHER_POSTS.value 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 >= EDIT_COMMUNITY_WIKI_POSTS.value) or
+ user.reputation >= EDIT_OTHER_POSTS.value 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 >= DELETE_COMMENTS.value 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 >= VIEW_OFFENSIVE_FLAGS.value 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 >= CLOSE_OWN_QUESTIONS.value) or
+ user.reputation >= CLOSE_OTHER_QUESTIONS.value 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 >= LOCK_POSTS.value 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 >= DISABLE_URL_NOFOLLOW.value
def can_accept_answer(user, question, answer):
return (user.is_authenticated() and
@@ -146,7 +203,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 >= REOPEN_OWN_QUESTIONS.value) or user.is_superuser
def can_delete_post(user, post):
if user.is_superuser:
@@ -182,7 +239,7 @@ 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 >= UPLOAD_FILES.value) or \
request_user.is_superuser
###########################################
@@ -205,7 +262,7 @@ def onFlaggedItem(item, post, user, timestamp=None):
post.save()
post.author.reputation = calculate_reputation(post.author.reputation,
- int(REPUTATION_RULES['lose_by_flagged']))
+ int(REPUTATION_RULES['lose_by_flagged'].value))
post.author.save()
question = post
@@ -213,33 +270,33 @@ def onFlaggedItem(item, post, user, timestamp=None):
question = post.question
reputation = Repute(user=post.author,
- negative=int(REPUTATION_RULES['lose_by_flagged']),
+ negative=int(REPUTATION_RULES['lose_by_flagged'].value),
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 == VOTE_RULES['scope_flags_invisible_main_page'].value :
post.author.reputation = calculate_reputation(post.author.reputation,
- int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']))
+ int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times'].value))
post.author.save()
reputation = Repute(user=post.author,
- negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']),
+ negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times'].value),
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 == VOTE_RULES['scope_flags_delete_post'].value :
post.author.reputation = calculate_reputation(post.author.reputation,
- int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']))
+ int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times'].value))
post.author.save()
reputation = Repute(user=post.author,
- negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']),
+ negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times'].value),
question=question,
reputed_at=timestamp,
reputation_type=-7,
@@ -268,10 +325,10 @@ def onAnswerAccept(answer, user, timestamp=None):
answer.question.save()
answer.author.reputation = calculate_reputation(answer.author.reputation,
- int(REPUTATION_RULES['gain_by_answer_accepted']))
+ int(REPUTATION_RULES['gain_by_answer_accepted'].value))
answer.author.save()
reputation = Repute(user=answer.author,
- positive=int(REPUTATION_RULES['gain_by_answer_accepted']),
+ positive=int(REPUTATION_RULES['gain_by_answer_accepted'].value),
question=answer.question,
reputed_at=timestamp,
reputation_type=2,
@@ -279,10 +336,10 @@ def onAnswerAccept(answer, user, timestamp=None):
reputation.save()
user.reputation = calculate_reputation(user.reputation,
- int(REPUTATION_RULES['gain_by_accepting_answer']))
+ int(REPUTATION_RULES['gain_by_accepting_answer'].value))
user.save()
reputation = Repute(user=user,
- positive=int(REPUTATION_RULES['gain_by_accepting_answer']),
+ positive=int(REPUTATION_RULES['gain_by_accepting_answer'].value),
question=answer.question,
reputed_at=timestamp,
reputation_type=3,
@@ -300,10 +357,10 @@ 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']))
+ int(REPUTATION_RULES['lose_by_accepted_answer_cancled'].value))
answer.author.save()
reputation = Repute(user=answer.author,
- negative=int(REPUTATION_RULES['lose_by_accepted_answer_cancled']),
+ negative=int(REPUTATION_RULES['lose_by_accepted_answer_cancled'].value),
question=answer.question,
reputed_at=timestamp,
reputation_type=-2,
@@ -311,10 +368,10 @@ def onAnswerAcceptCanceled(answer, user, timestamp=None):
reputation.save()
user.reputation = calculate_reputation(user.reputation,
- int(REPUTATION_RULES['lose_by_canceling_accepted_answer']))
+ int(REPUTATION_RULES['lose_by_canceling_accepted_answer'].value))
user.save()
reputation = Repute(user=user,
- negative=int(REPUTATION_RULES['lose_by_canceling_accepted_answer']),
+ negative=int(REPUTATION_RULES['lose_by_canceling_accepted_answer'].value),
question=answer.question,
reputed_at=timestamp,
reputation_type=-1,
@@ -334,9 +391,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 < int(REPUTATION_RULES['scope_per_day_by_upvotes'].value):
author.reputation = calculate_reputation(author.reputation,
- int(REPUTATION_RULES['gain_by_upvoted']))
+ int(REPUTATION_RULES['gain_by_upvoted'].value))
author.save()
question = post
@@ -344,7 +401,7 @@ def onUpVoted(vote, post, user, timestamp=None):
question = post.question
reputation = Repute(user=author,
- positive=int(REPUTATION_RULES['gain_by_upvoted']),
+ positive=int(REPUTATION_RULES['gain_by_upvoted'].value),
question=question,
reputed_at=timestamp,
reputation_type=1,
@@ -366,7 +423,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']))
+ int(REPUTATION_RULES['lose_by_upvote_canceled'].value))
author.save()
question = post
@@ -374,7 +431,7 @@ def onUpVotedCanceled(vote, post, user, timestamp=None):
question = post.question
reputation = Repute(user=author,
- negative=int(REPUTATION_RULES['lose_by_upvote_canceled']),
+ negative=int(REPUTATION_RULES['lose_by_upvote_canceled'].value),
question=question,
reputed_at=timestamp,
reputation_type=-8,
@@ -394,7 +451,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']))
+ int(REPUTATION_RULES['lose_by_downvoted'].value))
author.save()
question = post
@@ -402,7 +459,7 @@ def onDownVoted(vote, post, user, timestamp=None):
question = post.question
reputation = Repute(user=author,
- negative=int(REPUTATION_RULES['lose_by_downvoted']),
+ negative=int(REPUTATION_RULES['lose_by_downvoted'].value),
question=question,
reputed_at=timestamp,
reputation_type=-3,
@@ -410,11 +467,11 @@ def onDownVoted(vote, post, user, timestamp=None):
reputation.save()
user.reputation = calculate_reputation(user.reputation,
- int(REPUTATION_RULES['lose_by_downvoting']))
+ int(REPUTATION_RULES['lose_by_downvoting'].value))
user.save()
reputation = Repute(user=user,
- negative=int(REPUTATION_RULES['lose_by_downvoting']),
+ negative=int(REPUTATION_RULES['lose_by_downvoting'].value),
question=question,
reputed_at=timestamp,
reputation_type=-5,
@@ -436,7 +493,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']))
+ int(REPUTATION_RULES['gain_by_downvote_canceled'].value))
author.save()
question = post
@@ -444,7 +501,7 @@ def onDownVotedCanceled(vote, post, user, timestamp=None):
question = post.question
reputation = Repute(user=author,
- positive=int(REPUTATION_RULES['gain_by_downvote_canceled']),
+ positive=int(REPUTATION_RULES['gain_by_downvote_canceled'].value),
question=question,
reputed_at=timestamp,
reputation_type=4,
@@ -452,11 +509,11 @@ def onDownVotedCanceled(vote, post, user, timestamp=None):
reputation.save()
user.reputation = calculate_reputation(user.reputation,
- int(REPUTATION_RULES['gain_by_canceling_downvote']))
+ int(REPUTATION_RULES['gain_by_canceling_downvote'].value))
user.save()
reputation = Repute(user=user,
- positive=int(REPUTATION_RULES['gain_by_canceling_downvote']),
+ positive=int(REPUTATION_RULES['gain_by_canceling_downvote'].value),
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/const.py b/forum/const.py
index a3ab2a47..a3ab2a47 100755..100644
--- a/forum/const.py
+++ b/forum/const.py
diff --git a/forum/documentation/scratch b/forum/documentation/scratch
index ca4e67e9..948055fa 100644
--- a/forum/documentation/scratch
+++ b/forum/documentation/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..e4b929e9 100755..100644
--- a/forum/feed.py
+++ b/forum/feed.py
diff --git a/forum/forms.py b/forum/forms.py
index e0452d82..e0452d82 100755..100644
--- a/forum/forms.py
+++ b/forum/forms.py
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 372eb3aa..372eb3aa 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 42a6b3b3..42a6b3b3 100755..100644
--- a/forum/management/commands/send_email_alerts.py
+++ b/forum/management/commands/send_email_alerts.py
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/management/commands/sximport.py b/forum/management/commands/sximport.py
deleted file mode 100755
index f6af4e90..00000000
--- a/forum/management/commands/sximport.py
+++ /dev/null
@@ -1,109 +0,0 @@
-from django.core.management.base import LabelCommand
-from zipfile import ZipFile
-from xml.dom import minidom as dom
-import datetime
-
-from forum.models import User
-
-class Command(LabelCommand):
- def handle_label(self, label, **options):
- zip = ZipFile(label)
-
- map = {}
-
- map['users'] = self.import_users(zip.open("Users.xml"))
- map['questions'], map['answers'] = self.import_posts(zip.open("Posts.xml"))
-
-
- def row_to_dic(self, row):
- return dict([
- (child.localName.lower(),
- " ".join([t.nodeValue for t in child.childNodes if t.nodeType == t.TEXT_NODE]))
- for child in row.childNodes
- if child.nodeType == child.ELEMENT_NODE
- ])
-
- def from_sx_time(self, timestring):
- if timestring is None:
- return timestring
-
- try:
- return datetime.datetime.strptime(timestring, '%Y-%m-%dT%H:%M:%S')
- except:
- return datetime.datetime.strptime(timestring, '%Y-%m-%dT%H:%M:%S.%f')
-
-
- def import_users(self, users):
- pkey_map = {}
- doc = dom.parse(users)
-
- rows = doc.getElementsByTagName('row')
- unknown_count = 0
-
- added_names = []
-
- for row in rows:
- values = self.row_to_dic(row)
-
- username = values.get('displayname',
- values.get('realname',
- values.get('email', None)))
-
- if username is None:
- unknown_count += 1
- username = 'Unknown User %d' % unknown_count
-
- if username in added_names:
- cnt = 1
- new_username = "%s %d" % (username, cnt)
- while new_username in added_names:
- cnt += 1
- new_username = "%s %d" % (username, cnt)
-
- username = new_username
-
- added_names.append(username)
-
- user = User(username=username, email=values.get('email', ''))
-
- user.reputation = values['reputation']
- user.last_seen = self.from_sx_time(values['lastaccessdate'])
-
- user.real_name = values.get('realname', '')
- user.about = values.get('aboutme', '')
- user.website = values.get('websiteurl', '')
- user.date_of_birth = self.from_sx_time(values.get('birthday', None))
- user.location = values.get('location', '')
-
- user.is_active = True
- user.email_isvalid = True
-
-
- if int(values['usertypeid']) == 5:
- user.is_superuser = True
-
- if int(values['usertypeid']) == 5:
- user.is_staff = True
-
- user.save()
-
- pkey_map[values['id']] = user
-
- return users
-
- def import_posts(self, posts, map):
- pkey_map = {}
- doc = dom.parse(posts)
-
- rows = doc.getElementsByTagName('row')
-
- for row in rows:
- map = {
- 'title': row['']
- }
-
- pass
- pass
-
-
-
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..866734da 100755..100644
--- a/forum/middleware/anon_user.py
+++ b/forum/middleware/anon_user.py
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..d0d2d4a7 100755..100644
--- a/forum/models/__init__.py
+++ b/forum/models/__init__.py
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..4d3154b0 100755..100644
--- a/forum/models/question.py
+++ b/forum/models/question.py
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/settings.py b/forum/settings.py
index 04a7c399..ac02b1ea 100755..100644
--- a/forum/settings.py
+++ b/forum/settings.py
@@ -1,6 +1,5 @@
import os
-
INSTALLED_APPS = ['forum']
MIDDLEWARE_CLASSES = [
@@ -47,5 +46,3 @@ def setup_settings(settings):
settings.TEMPLATE_LOADERS = set(settings.TEMPLATE_LOADERS) | set(TEMPLATE_LOADERS)
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
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..10b6a340 100755..100644
--- a/forum/skins/__init__.py
+++ b/forum/skins/__init__.py
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 b687889f..b687889f 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..86f2e9df 100755..100644
--- a/forum/templatetags/extra_tags.py
+++ b/forum/templatetags/extra_tags.py
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..d8d04d05 100755..100644
--- a/forum/utils/forms.py
+++ b/forum/utils/forms.py
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 b312242d..ffe1c34f 100755..100644
--- a/forum/views/commands.py
+++ b/forum/views/commands.py
@@ -138,7 +138,7 @@ 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) >= auth.VOTE_RULES['scope_deny_unvote_days'].value:
response_data['status'] = 2
else:
voted = vote.vote
@@ -152,7 +152,7 @@ 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) >= auth.VOTE_RULES['scope_votes_per_user_per_day'].value:
response_data['allowed'] = -3
else:
vote = Vote(user=request.user, content_object=post, vote=vote_score, voted_at=datetime.datetime.now())
@@ -163,8 +163,8 @@ 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 = auth.VOTE_RULES['scope_votes_per_user_per_day'].value - Vote.objects.get_votes_count_today_from_user(request.user)
+ if votes_left <= auth.VOTE_RULES['scope_warn_votes_left'].value:
response_data['message'] = u'%s votes left' % votes_left
response_data['count'] = post.score
elif vote_type in ['7', '8']:
@@ -174,7 +174,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) >= auth.VOTE_RULES['scope_flags_per_user_per_day'].value:
response_data['allowed'] = -3
elif not auth.can_flag_offensive(request.user):
response_data['allowed'] = -2
diff --git a/forum/views/meta.py b/forum/views/meta.py
index 40f3d394..40f3d394 100755..100644
--- a/forum/views/meta.py
+++ b/forum/views/meta.py
diff --git a/forum/views/users.py b/forum/views/users.py
index 113c46e6..0cefc598 100755..100644
--- a/forum/views/users.py
+++ b/forum/views/users.py
@@ -216,7 +216,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 = auth.VOTE_RULES['scope_votes_per_user_per_day'].value
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 57c8e043..57c8e043 100755..100644
--- a/forum/views/writers.py
+++ b/forum/views/writers.py
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..e56c8279
--- /dev/null
+++ b/livesettings/templates/livesettings/group_settings.html
@@ -0,0 +1,56 @@
+{% 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.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 }}</p>
+ {% endif %}
+ {% if field.field.default_text %}
+ <p class="help">{{ field.field.default_text|break_at:40}}</p>
+ {% endif %}
+ </td>
+ <td>{{ field }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
+<input type="submit" value="Save" class="default" />
+</form>
+{% else %}
+ <p>{% trans "You don't have permission to edit values." %}</p>
+{% endif %}
+</div>
+{% 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..1fed730d
--- /dev/null
+++ b/livesettings/templatetags/config_tags.py
@@ -0,0 +1,89 @@
+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"""
+
+ 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..c55c92e4
--- /dev/null
+++ b/livesettings/urls.py
@@ -0,0 +1,7 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('livesettings.views',
+ (r'^$', 'site_settings', {}, 'satchmo_site_settings'),
+ (r'^export/$', 'export_as_python', {}, 'settings_export'),
+ (r'^(?P<group>[^/]+)/$', '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..66c4adaa
--- /dev/null
+++ b/livesettings/views.py
@@ -0,0 +1,91 @@
+from django.http import HttpResponseRedirect
+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()
+ if group is None:
+ settings = mgr
+ title = 'Site settings'
+ else:
+ 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, {
+ 'title': title,
+ 'group' : group,
+ '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):
+ 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 f9b776cd..97d80e97 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,16 +72,19 @@ 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',
'debug_toolbar',
- #'django_evolution',
'forum',
'django_authopenid',
'debug_toolbar' ,
#'forum.importers.stackexchange', #se loader
'south',
+ 'livesettings',
+ 'keyedcache',
)
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',)
@@ -83,7 +92,7 @@ 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/urls.py b/urls.py
index 3ebc19e8..f73eb497 100644
--- a/urls.py
+++ b/urls.py
@@ -1,6 +1,7 @@
from django.conf.urls.defaults import *
from django.utils.translation import ugettext as _
from django.conf import settings
+import livesettings
from django.contrib import admin
admin.autodiscover()
@@ -8,4 +9,5 @@ admin.autodiscover()
urlpatterns = patterns('',
(r'^%s' % settings.FORUM_SCRIPT_ALIAS, include('forum.urls')),
(r'^admin/', include(admin.site.urls)),
+ (r'^settings/', include('livesettings.urls')),
)