diff options
Diffstat (limited to 'Pootle-2.0.0/local_apps/pootle_misc')
14 files changed, 654 insertions, 0 deletions
diff --git a/Pootle-2.0.0/local_apps/pootle_misc/__init__.py b/Pootle-2.0.0/local_apps/pootle_misc/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Pootle-2.0.0/local_apps/pootle_misc/__init__.py diff --git a/Pootle-2.0.0/local_apps/pootle_misc/baseurl.py b/Pootle-2.0.0/local_apps/pootle_misc/baseurl.py new file mode 100644 index 0000000..fa2e2b2 --- /dev/null +++ b/Pootle-2.0.0/local_apps/pootle_misc/baseurl.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2008-2009 Zuza Software Foundation +# +# This file is part of Pootle. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. + +""" utility functions to help deploy Pootle under different url prefixes """ + +import urllib + +from django.conf import settings +from django.http import HttpResponseRedirect + +def l(path): + """ filter urls adding base_path prefix if required """ + if path and path.startswith('/'): + base_url = getattr(settings, "SCRIPT_NAME", "") + #if not path.startswith(base_url): + return base_url + path + return path + +def abs_l(path): + """ filter paths adding full url prefix if required """ + return settings.BASE_URL + path + +def m(path): + """ filter urls adding media url prefix if required """ + return l(settings.MEDIA_URL + path) + +def redirect(url, **kwargs): + if len(kwargs) > 0: + return HttpResponseRedirect(l('%s?%s' % (url, urllib.urlencode(kwargs)))) + else: + return HttpResponseRedirect(l(url)) diff --git a/Pootle-2.0.0/local_apps/pootle_misc/context_processors.py b/Pootle-2.0.0/local_apps/pootle_misc/context_processors.py new file mode 100644 index 0000000..290a539 --- /dev/null +++ b/Pootle-2.0.0/local_apps/pootle_misc/context_processors.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2009 Zuza Software Foundation +# +# This file is part of Pootle. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. + +from django.conf import settings + +def sitesettings(request): + """exposes settings to templates""" + #FIXME: maybe we should expose relevant settings only? + return {'settings': settings } diff --git a/Pootle-2.0.0/local_apps/pootle_misc/dbinit.py b/Pootle-2.0.0/local_apps/pootle_misc/dbinit.py new file mode 100644 index 0000000..94b0893 --- /dev/null +++ b/Pootle-2.0.0/local_apps/pootle_misc/dbinit.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2008-2009 Zuza Software Foundation +# +# This file is part of Pootle. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. + +import sys + +from django.core.management import call_command +from django.core.exceptions import ObjectDoesNotExist +from django.contrib.auth.models import User + +from pootle.i18n.gettext import ugettext as _ + +from pootle_app.models import Language, Project + + +def header(exception): + text = """ + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> + <html> + <head> + <title>%(title)s</title> + <meta content="text/html; charset=utf-8" http-equiv="content-type" /> + <style type="text/css"> + body + { + background-color: #ffffff; + color: #000000; + font-family: Georgia, serif; + margin: 40px auto; + width: 740px; + } + h1 + { + font-size: 185%%; + } + ul + { + list-style-type: square; + } + .error + { + background-color: inherit; + color: #d54e21; + font-weight: bold; + } + </style> + </head> + <body> + <h1>%(title)s</h1> + <p class="error">%(msg)s</p> + """ % {'title': _('Pootle: Install'), + 'msg': _('Error: "%s" while attempting to access the Pootle database, will try to initialize database.', exception)} + return text + +def syncdb(): + text = u""" + <p>%s</p> + """ % _('Creating database tables...') + call_command('syncdb', interactive=False) + return text + +def initdb(): + text = u""" + <p>%s</p> + """ % _('Creating default languages, projects and admin user') + call_command('initdb') + return text + +def stats_start(): + text = u""" + <p>%s</p> + <ul> + """ % _('Calculating translation statistics, this will take a few minutes') + return text + +def stats_language(language): + text = u""" + <li>%s</li> + """ % _('%(language)s is %(percent)d%% complete', + {'language': language.localname(), 'percent': language.translated_percentage()}) + return text + +def stats_project(project): + text = u""" + <li>%s</li> + """ % _('Project %(project)s is %(percent)d%% complete', + {'project': project.fullname, 'percent': project.translated_percentage()}) + return text + +def stats_end(): + text = u""" + </ul> + <p>%s</p> + """ % _('Done calculating statistics for default languages and projects') + return text + + +def footer(): + text = """ + <p>%(endmsg)s</p> + <div><script>setTimeout("location.reload()", 10000)</script></div> + </body></html> + """ % { 'endmsg': _('Initialized database, you will be redirected to the front page in 10 seconds') } + return text + +def staggered_install(exception): + """Initialize the pootle database without displaying progress + reports for each step""" + + # django's syncdb command prints progress repots to stdout, but + # mod_wsgi doesn't like stdout, so we reroute to stderr + stdout = sys.stdout + sys.stdout = sys.stderr + + yield header(exception) + + # try to build the database tables + yield syncdb() + + # if this is a fresh install we should add some default languages + # and projects and a default admin account to make pootle more + # usable out of the box + # + # if there are no user accounts apart from defaults then assume + # it's fresh install + if User.objects.hide_defaults().count() == 0: + yield initdb() + + # first time to visit the front page all stats for projects and + # languages will be calculated which can take forever, since users + # don't like webpages that take forever let's precalculate the + # stats here + yield stats_start() + for language in Language.objects.all(): + yield stats_language(language) + for project in Project.objects.all(): + yield stats_project(project) + yield stats_end() + + yield footer() + + # bring back stdout + sys.stdout = stdout + return diff --git a/Pootle-2.0.0/local_apps/pootle_misc/middleware/__init__.py b/Pootle-2.0.0/local_apps/pootle_misc/middleware/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Pootle-2.0.0/local_apps/pootle_misc/middleware/__init__.py diff --git a/Pootle-2.0.0/local_apps/pootle_misc/middleware/baseurl.py b/Pootle-2.0.0/local_apps/pootle_misc/middleware/baseurl.py new file mode 100644 index 0000000..a5c6640 --- /dev/null +++ b/Pootle-2.0.0/local_apps/pootle_misc/middleware/baseurl.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2008-2009 Zuza Software Foundation +# +# This file is part of Pootle. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. + +from django.conf import settings + +class BaseUrlMiddleware(object): + def process_request(self, request): + """calculate settings.BASEURL based on HTTP headers""" + domain = None + + if 'HTTP_HOST' in request.META: + domain = request.META['HTTP_HOST'] + + if 'SCRIPT_NAME' in request.META: + settings.SCRIPT_NAME = request.META['SCRIPT_NAME'] + if domain is not None: + domain += request.META['SCRIPT_NAME'] + + if domain is not None: + if request.is_secure(): + settings.BASE_URL = 'https://' + domain + else: + settings.BASE_URL = 'http://' + domain + + #FIXME: DIRTY HACK ALERT if this works then something is + #wrong with the universe + # poison sites cache using detected domain + from django.contrib.sites import models as sites_models + sites_models.SITE_CACHE[settings.SITE_ID] = sites_models.Site(settings.SITE_ID, + request.META['HTTP_HOST'], + settings.TITLE) diff --git a/Pootle-2.0.0/local_apps/pootle_misc/middleware/errorpages.py b/Pootle-2.0.0/local_apps/pootle_misc/middleware/errorpages.py new file mode 100644 index 0000000..e165e1e --- /dev/null +++ b/Pootle-2.0.0/local_apps/pootle_misc/middleware/errorpages.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2008-2009 Zuza Software Foundation +# +# This file is part of Pootle. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. + +import traceback +import sys + +from django.core.exceptions import PermissionDenied +from django.http import HttpResponseForbidden, HttpResponseServerError +from django.http import Http404 +from django.template.loader import render_to_string +from django.template import RequestContext +from django.utils.translation import ugettext as _ +from django.conf import settings +from django.core.mail import mail_admins + +from pootle_misc.baseurl import l + +class ErrorPagesMiddleware(object): + """ + Friendlier Error Pages + """ + def process_exception(self, request, exception): + if isinstance(exception, Http404): + pass + elif isinstance(exception, PermissionDenied): + templatevars = { 'permission_error': unicode(exception.args[0]) } + if not request.user.is_authenticated(): + login_msg = _('You need to <a href="%(login_link)s">login</a> to access this page.' % { + 'login_link': l("/accounts/login/") }) + templatevars["login_message"] = login_msg + return HttpResponseForbidden(render_to_string('403.html', templatevars, + RequestContext(request))) + else: + #FIXME: implement better 500 + tb = traceback.format_exc() + print >> sys.stderr, tb + if not settings.DEBUG: + try: + templatevars = {'exception': unicode(exception.args[0])} + if hasattr(exception, 'filename'): + templatevars['fserror'] = _('Error accessing %(filename)s, Filesystem sent error: %(errormsg)s', + {'filename': exception.filename, 'errormsg': exception.strerror}) + + # send email to admins with details about exception + subject = 'Error (%s IP): %s' % ((request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'), request.path) + try: + request_repr = repr(request) + except: + request_repr = "Request repr() unavailable" + message = "%s\n\n%s\n\n%s" % (unicode(exception.args[0]), tb, request_repr) + mail_admins(subject, message, fail_silently=True) + return HttpResponseServerError(render_to_string('500.html', templatevars, + RequestContext(request))) + except: + # let's not confuse things by throwing an exception here + pass diff --git a/Pootle-2.0.0/local_apps/pootle_misc/middleware/siteconfig.py b/Pootle-2.0.0/local_apps/pootle_misc/middleware/siteconfig.py new file mode 100644 index 0000000..7db632b --- /dev/null +++ b/Pootle-2.0.0/local_apps/pootle_misc/middleware/siteconfig.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2008-2009 Zuza Software Foundation +# +# This file is part of Pootle. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. + +from django.http import HttpResponse +from pootle_misc import siteconfig +from pootle_misc import dbinit + +"""some unused http status code to mark the need to auto install""" + +INSTALL_STATUS_CODE = 613 +class SiteConfigMiddleware(object): + """ + This middleware does two things, it reload djblet siteconfigs on + every request to ensure they're uptodate. but also works as an + early detection system for database errors. + + It might seem strange that the middleware does these two unrelated + tasks, but since the only way to test the database is to run a + query, it would be too wasteful to add another query per request + when siteconfig already requires one. + + """ + def process_request(self, request): + """load site config, return a dummy response if database seems uninitialized""" + #FIXME: can't we find a more efficient method? + try: + siteconfig.load_site_config() + except Exception, e: + #HACKISH: since exceptions thrown by different databases + # do not share the same class heirarchy (DBAPI2 sucks) we + # have to check the class name instead (since python uses + # duck typing I will call this + # poking-the-duck-until-it-quacks-like-a-duck-test + + if e.__class__.__name__ in ['OperationalError', 'ProgrammingError']: + # we can't build the database here cause caching + # middleware won't allow progressive loading of + # response so instead return an empty response marked + # with special status code INSTALL_STATUS_CODE + + response = HttpResponse() + response.status_code = INSTALL_STATUS_CODE + response.exception = e + return response + + def process_response(self, request, response): + """ this should be the last response processor to run, detect + dummy response with INSTALL_STATUS_CODE status code and start + db install process""" + + if response.status_code == INSTALL_STATUS_CODE: + return HttpResponse(dbinit.staggered_install(response.exception)) + else: + return response + diff --git a/Pootle-2.0.0/local_apps/pootle_misc/siteconfig.py b/Pootle-2.0.0/local_apps/pootle_misc/siteconfig.py new file mode 100644 index 0000000..3d4f9ea --- /dev/null +++ b/Pootle-2.0.0/local_apps/pootle_misc/siteconfig.py @@ -0,0 +1,51 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright 2004-2009 Zuza Software Foundation +# +# This file is part of Pootle. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. + +# NOTE: Import this file in your urls.py or some place before +# any code relying on settings is imported. + +from django.contrib.sites.models import Site + +from djblets.siteconfig.models import SiteConfiguration +from djblets.siteconfig.django_settings import apply_django_settings, generate_defaults + +settings_map = { + # siteconfig key settings.py key + 'DESCRIPTION': 'DESCRIPTION', + 'TITLE' : 'TITLE', +} + +defaults = generate_defaults(settings_map) + +def load_site_config(): + """Sets up the SiteConfiguration, provides defaults and syncs settings.""" + try: + siteconfig = SiteConfiguration.objects.get_current() + except SiteConfiguration.DoesNotExist: + # Either warn or just create the thing. Depends on your app + siteconfig = SiteConfiguration(site=Site.objects.get_current(), + version="1.0") + siteconfig.save() + + # Code will go here for settings work in later examples. + if not siteconfig.get_defaults(): + siteconfig.add_defaults(defaults) + apply_django_settings(siteconfig, settings_map) + return siteconfig diff --git a/Pootle-2.0.0/local_apps/pootle_misc/templatetags/__init__.py b/Pootle-2.0.0/local_apps/pootle_misc/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Pootle-2.0.0/local_apps/pootle_misc/templatetags/__init__.py diff --git a/Pootle-2.0.0/local_apps/pootle_misc/templatetags/baseurl.py b/Pootle-2.0.0/local_apps/pootle_misc/templatetags/baseurl.py new file mode 100644 index 0000000..877fb72 --- /dev/null +++ b/Pootle-2.0.0/local_apps/pootle_misc/templatetags/baseurl.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2008-2009 Zuza Software Foundation +# +# This file is part of Pootle. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. + +from django import template +from django.template.defaultfilters import stringfilter + +from pootle_misc.baseurl import l, m, abs_l + +register = template.Library() +register.filter('l', stringfilter(l)) +register.filter('m', stringfilter(m)) +register.filter('abs_l', stringfilter(abs_l)) diff --git a/Pootle-2.0.0/local_apps/pootle_misc/templatetags/cleanhtml.py b/Pootle-2.0.0/local_apps/pootle_misc/templatetags/cleanhtml.py new file mode 100644 index 0000000..40d9e60 --- /dev/null +++ b/Pootle-2.0.0/local_apps/pootle_misc/templatetags/cleanhtml.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2008-2009 Zuza Software Foundation +# +# This file is part of Pootle. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. + + +from django import template +from django.template.defaultfilters import stringfilter +from django.utils.safestring import mark_safe + +try: + from lxml.html.clean import clean_html +except ImportError: + clean_html = lambda text: text + +def clean_wrapper(text): + """wrapper around lxml's html cleaner that returns SafeStrings for + immediate rendering in templates""" + return mark_safe(clean_html(text)) + +register = template.Library() +register.filter('clean', stringfilter(clean_wrapper)) + diff --git a/Pootle-2.0.0/local_apps/pootle_misc/templatetags/render_pager.py b/Pootle-2.0.0/local_apps/pootle_misc/templatetags/render_pager.py new file mode 100644 index 0000000..86961b6 --- /dev/null +++ b/Pootle-2.0.0/local_apps/pootle_misc/templatetags/render_pager.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2008-2009 Zuza Software Foundation +# +# This file is part of Pootle. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. + +from django.utils.translation import ugettext as _ +from django import template +from django.utils.safestring import mark_safe + +def render_pager(pager): + """Render a pager block with next and previous links""" + if not pager.has_other_pages(): + return "" + + result = '<ul class="pager">' + if pager.has_previous(): + result += '<li><a href="?page=1" class="nth-link">%s</a></li>' % _('First') + result += '<li><a href="?page=%d" class="prevnext-link">%s</a></li>' % (pager.previous_page_number(), _('Previous')) + + start = max(1, pager.number - 4) + end = min(pager.paginator.num_pages, pager.number + 4) + if start > 1: + result += '<li>...</li>' + for i in range(start, end+1): + if i == pager.number: + result += '<li><span class="current-link">%s</span></li>' % i + else: + result += '<li><a href="?page=%d" class="number-link">%d</a></li>' % (i, i) + if end < pager.paginator.num_pages: + result += '<li>...</li>' + + if pager.has_next(): + result += '<li><a href="?page=%d" class="prevnext-link">%s</a></li>' % (pager.next_page_number(), _('Next')) + result += '<li><a href="?page=%d" class="nth-link">%s</a></li>' % (pager.paginator.num_pages, _('Last')) + + result += '</ul>' + return mark_safe(result) + +register = template.Library() +register.filter('render_pager', render_pager) + diff --git a/Pootle-2.0.0/local_apps/pootle_misc/util.py b/Pootle-2.0.0/local_apps/pootle_misc/util.py new file mode 100644 index 0000000..11b5c44 --- /dev/null +++ b/Pootle-2.0.0/local_apps/pootle_misc/util.py @@ -0,0 +1,52 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright 2004-2009 Zuza Software Foundation +# +# This file is part of Pootle. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. + +from django.core.cache import cache +from django.conf import settings +import logging + +def getfromcache(function, timeout=settings.OBJECT_CACHE_TIMEOUT): + def _getfromcache(instance, *args, **kwargs): + key = instance.pootle_path + ":" + function.__name__ + result = cache.get(key) + if result is None: + logging.debug("cache miss for %s", key) + result = function(instance, *args, **kwargs) + cache.set(key, result, timeout) + return result + return _getfromcache + +def deletefromcache(sender, functions, **kwargs): + path = sender.pootle_path + path_parts = path.split("/") + + # clean project cache + if len(path_parts): + key = "/projects/%s/" % path_parts[2] + for func in functions: + cache.delete(key + ":"+func) + + # clean store and directory cache + while path_parts: + for func in functions: + cache.delete(path + ":"+func) + path_parts = path_parts[:-1] + path = "/".join(path_parts) + "/" + |