Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/Pootle-2.0.0/external_apps
diff options
context:
space:
mode:
Diffstat (limited to 'Pootle-2.0.0/external_apps')
-rw-r--r--Pootle-2.0.0/external_apps/djblets/__init__.py0
-rw-r--r--Pootle-2.0.0/external_apps/djblets/siteconfig/__init__.py0
-rw-r--r--Pootle-2.0.0/external_apps/djblets/siteconfig/admin.py35
-rw-r--r--Pootle-2.0.0/external_apps/djblets/siteconfig/context_processors.py36
-rw-r--r--Pootle-2.0.0/external_apps/djblets/siteconfig/django_settings.py170
-rw-r--r--Pootle-2.0.0/external_apps/djblets/siteconfig/forms.py76
-rw-r--r--Pootle-2.0.0/external_apps/djblets/siteconfig/managers.py71
-rw-r--r--Pootle-2.0.0/external_apps/djblets/siteconfig/middleware.py13
-rw-r--r--Pootle-2.0.0/external_apps/djblets/siteconfig/models.py127
-rw-r--r--Pootle-2.0.0/external_apps/djblets/siteconfig/views.py58
-rw-r--r--Pootle-2.0.0/external_apps/djblets/util/__init__.py24
-rw-r--r--Pootle-2.0.0/external_apps/djblets/util/context_processors.py69
-rw-r--r--Pootle-2.0.0/external_apps/djblets/util/dates.py57
-rw-r--r--Pootle-2.0.0/external_apps/djblets/util/db.py51
-rw-r--r--Pootle-2.0.0/external_apps/djblets/util/dbevolution.py33
-rw-r--r--Pootle-2.0.0/external_apps/djblets/util/decorators.py177
-rw-r--r--Pootle-2.0.0/external_apps/djblets/util/fields.py208
-rw-r--r--Pootle-2.0.0/external_apps/djblets/util/http.py80
-rw-r--r--Pootle-2.0.0/external_apps/djblets/util/misc.py269
-rw-r--r--Pootle-2.0.0/external_apps/djblets/util/rooturl.py15
-rw-r--r--Pootle-2.0.0/external_apps/djblets/util/templatetags/__init__.py0
-rw-r--r--Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_deco.py57
-rw-r--r--Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_email.py71
-rw-r--r--Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_forms.py95
-rw-r--r--Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_images.py100
-rw-r--r--Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_js.py59
-rw-r--r--Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_utils.py283
-rw-r--r--Pootle-2.0.0/external_apps/djblets/util/testing.py57
-rw-r--r--Pootle-2.0.0/external_apps/djblets/util/tests.py243
-rw-r--r--Pootle-2.0.0/external_apps/profiles/__init__.py0
-rw-r--r--Pootle-2.0.0/external_apps/profiles/urls.py43
-rw-r--r--Pootle-2.0.0/external_apps/profiles/utils.py45
-rw-r--r--Pootle-2.0.0/external_apps/profiles/views.py337
-rw-r--r--Pootle-2.0.0/external_apps/registration/__init__.py0
-rw-r--r--Pootle-2.0.0/external_apps/registration/admin.py11
-rw-r--r--Pootle-2.0.0/external_apps/registration/forms.py134
-rw-r--r--Pootle-2.0.0/external_apps/registration/models.py255
-rw-r--r--Pootle-2.0.0/external_apps/registration/signals.py8
-rw-r--r--Pootle-2.0.0/external_apps/registration/tests.py355
-rw-r--r--Pootle-2.0.0/external_apps/registration/urls.py72
-rw-r--r--Pootle-2.0.0/external_apps/registration/views.py153
41 files changed, 3947 insertions, 0 deletions
diff --git a/Pootle-2.0.0/external_apps/djblets/__init__.py b/Pootle-2.0.0/external_apps/djblets/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/__init__.py
diff --git a/Pootle-2.0.0/external_apps/djblets/siteconfig/__init__.py b/Pootle-2.0.0/external_apps/djblets/siteconfig/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/siteconfig/__init__.py
diff --git a/Pootle-2.0.0/external_apps/djblets/siteconfig/admin.py b/Pootle-2.0.0/external_apps/djblets/siteconfig/admin.py
new file mode 100644
index 0000000..e03cdcd
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/siteconfig/admin.py
@@ -0,0 +1,35 @@
+#
+# djblets/siteconfig/admin.py
+#
+# Copyright (c) 2008 Christian Hammond
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+from django.contrib import admin
+
+from djblets.siteconfig.models import SiteConfiguration
+
+
+class SiteConfigurationAdmin(admin.ModelAdmin):
+ list_display = ('site', 'version')
+
+
+admin.site.register(SiteConfiguration, SiteConfigurationAdmin)
diff --git a/Pootle-2.0.0/external_apps/djblets/siteconfig/context_processors.py b/Pootle-2.0.0/external_apps/djblets/siteconfig/context_processors.py
new file mode 100644
index 0000000..a8f846d
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/siteconfig/context_processors.py
@@ -0,0 +1,36 @@
+#
+# djblets/siteconfig/context_processors.py
+#
+# Copyright (c) 2008 Christian Hammond
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+from djblets.siteconfig.models import SiteConfiguration
+
+
+def siteconfig(request):
+ """
+ Exposes the site configuration as a siteconfig variable in templates.
+ """
+ try:
+ return {'siteconfig': SiteConfiguration.objects.get_current()}
+ except:
+ return {'siteconfig': None}
diff --git a/Pootle-2.0.0/external_apps/djblets/siteconfig/django_settings.py b/Pootle-2.0.0/external_apps/djblets/siteconfig/django_settings.py
new file mode 100644
index 0000000..a38c573
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/siteconfig/django_settings.py
@@ -0,0 +1,170 @@
+#
+# djblets/siteconfig/django_settings.py
+#
+# Copyright (c) 2008 Christian Hammond
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+import os
+import time
+
+from django.conf import settings
+
+
+locale_settings_map = {
+ 'locale_timezone': { 'key': 'TIME_ZONE',
+ 'deserialize_func': str },
+ 'locale_language_code': 'LANGUAGE_CODE',
+ 'locale_date_format': 'DATE_FORMAT',
+ 'locale_datetime_format': 'DATETIME_FORMAT',
+ 'locale_default_charset': { 'key': 'DEFAULT_CHARSET',
+ 'deserialize_func': str },
+ 'locale_language_code': 'LANGUAGE_CODE',
+ 'locale_month_day_format': 'MONTH_DAY_FORMAT',
+ 'locale_time_format': 'TIME_FORMAT',
+ 'locale_year_month_format': 'YEAR_MONTH_FORMAT',
+}
+
+mail_settings_map = {
+ 'mail_server_address': 'SERVER_EMAIL',
+ 'mail_default_from': 'DEFAULT_FROM_EMAIL',
+ 'mail_host': 'EMAIL_HOST',
+ 'mail_port': 'EMAIL_PORT',
+ 'mail_host_user': 'EMAIL_HOST_USER',
+ 'mail_host_password': 'EMAIL_HOST_PASSWORD',
+ 'mail_use_tls': 'EMAIL_USE_TLS',
+}
+
+site_settings_map = {
+ 'site_media_root': 'MEDIA_ROOT',
+ 'site_media_url': 'MEDIA_URL',
+ 'site_prepend_www': 'PREPEND_WWW',
+ 'site_upload_temp_dir': 'FILE_UPLOAD_TEMP_DIR',
+ 'site_upload_max_memory_size': 'FILE_UPLOAD_MAX_MEMORY_SIZE',
+}
+
+cache_settings_map = {
+ 'cache_backend': 'CACHE_BACKEND',
+ 'cache_expiration_time': 'CACHE_EXPIRATION_TIME',
+}
+
+
+# Don't build unless we need it.
+_django_settings_map = {}
+
+
+def get_django_settings_map():
+ """
+ Returns the settings map for all Django settings that users may need
+ to customize.
+ """
+ if not _django_settings_map:
+ _django_settings_map.update(locale_settings_map)
+ _django_settings_map.update(mail_settings_map)
+ _django_settings_map.update(site_settings_map)
+ _django_settings_map.update(cache_settings_map)
+
+ return _django_settings_map
+
+
+def generate_defaults(settings_map):
+ """
+ Utility function to generate a defaults mapping.
+ """
+ defaults = {}
+
+ for siteconfig_key, setting_data in settings_map.iteritems():
+ if isinstance(setting_data, dict):
+ setting_key = setting_data['key']
+ else:
+ setting_key = setting_data
+
+ if hasattr(settings, setting_key):
+ defaults[siteconfig_key] = getattr(settings, setting_key)
+
+ return defaults
+
+
+def get_locale_defaults():
+ """
+ Returns the locale-related Django defaults that projects may want to
+ let users customize.
+ """
+ return generate_defaults(locale_settings_map)
+
+
+def get_mail_defaults():
+ """
+ Returns the mail-related Django defaults that projects may want to
+ let users customize.
+ """
+ return generate_defaults(mail_settings_map)
+
+
+def get_site_defaults():
+ """
+ Returns the site-related Django defaults that projects may want to
+ let users customize.
+ """
+ return generate_defaults(site_settings_map)
+
+
+def get_cache_defaults():
+ """
+ Returns the cache-related Django defaults that projects may want to
+ let users customize.
+ """
+ return generate_defaults(cache_settings_map)
+
+
+def get_django_defaults():
+ """
+ Returns all Django defaults that projects may want to let users customize.
+ """
+ return generate_defaults(get_django_settings_map())
+
+
+def apply_django_settings(siteconfig, settings_map=None):
+ """
+ Applies all settings from the site configuration to the Django settings
+ object.
+ """
+ if settings_map is None:
+ settings_map = get_django_settings_map()
+
+ for key, setting_data in settings_map.iteritems():
+ if key in siteconfig.settings:
+ value = siteconfig.get(key)
+
+ if isinstance(setting_data, dict):
+ setting_key = setting_data['key']
+
+ if ('deserialize_func' in setting_data and
+ callable(setting_data['deserialize_func'])):
+ value = setting_data['deserialize_func'](value)
+ else:
+ setting_key = setting_data
+
+ setattr(settings, setting_key, value)
+
+ if hasattr(time, 'tzset'):
+ os.environ['TZ'] = settings.TIME_ZONE
+ time.tzset()
diff --git a/Pootle-2.0.0/external_apps/djblets/siteconfig/forms.py b/Pootle-2.0.0/external_apps/djblets/siteconfig/forms.py
new file mode 100644
index 0000000..ec75863
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/siteconfig/forms.py
@@ -0,0 +1,76 @@
+#
+# djblets/siteconfig/forms.py
+#
+# Copyright (c) 2008 Christian Hammond
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+from django import forms
+
+
+class SiteSettingsForm(forms.Form):
+ """
+ A base form for loading/saving settings for a SiteConfiguration. This is
+ meant to be subclassed for different settings pages. Any fields defined
+ by the form will be loaded/saved automatically.
+ """
+ def __init__(self, siteconfig, *args, **kwargs):
+ forms.Form.__init__(self, *args, **kwargs)
+ self.siteconfig = siteconfig
+ self.disabled_fields = {}
+ self.disabled_reasons = {}
+
+ self.load()
+
+ def load(self):
+ """
+ Loads settings from the ```SiteConfiguration''' into this form.
+ The default values in the form will be the values in the settings.
+
+ This also handles setting disabled fields based on the
+ ```disabled_fields''' and ```disabled_reasons''' variables set on
+ this form.
+ """
+ if hasattr(self, "Meta"):
+ save_blacklist = getattr(self.Meta, "save_blacklist", [])
+
+ for field in self.fields:
+ value = self.siteconfig.get(field)
+
+ if isinstance(value, bool) or value:
+ self.fields[field].initial = value
+
+ if field in self.disabled_fields:
+ self.fields[field].widget.attrs['disabled'] = 'disabled'
+
+ def save(self):
+ """
+ Saves settings from the form back into the ```SiteConfiguration'''.
+ """
+ if not self.errors:
+ if hasattr(self, "Meta"):
+ save_blacklist = getattr(self.Meta, "save_blacklist", [])
+
+ for key, value in self.cleaned_data.iteritems():
+ if key not in save_blacklist:
+ self.siteconfig.settings[key] = value
+
+ self.siteconfig.save()
diff --git a/Pootle-2.0.0/external_apps/djblets/siteconfig/managers.py b/Pootle-2.0.0/external_apps/djblets/siteconfig/managers.py
new file mode 100644
index 0000000..feac99e
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/siteconfig/managers.py
@@ -0,0 +1,71 @@
+#
+# djblets/siteconfig/managers.py
+#
+# Copyright (c) 2008 Christian Hammond
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+from django.contrib.sites.models import Site
+from django.db import models
+
+
+_SITECONFIG_CACHE = {}
+
+
+class SiteConfigurationManager(models.Manager):
+ """
+ A Manager that provides a get_current function for retrieving the
+ SiteConfiguration for this particular running site.
+ """
+ def get_current(self):
+ """
+ Returns the site configuration on the active site.
+ """
+ from djblets.siteconfig.models import SiteConfiguration
+ global _SITECONFIG_CACHE
+
+ # This will handle raising a ImproperlyConfigured if not set up
+ # properly.
+ site = Site.objects.get_current()
+
+ if site.id not in _SITECONFIG_CACHE:
+ _SITECONFIG_CACHE[site.id] = \
+ SiteConfiguration.objects.get(site=site)
+
+ return _SITECONFIG_CACHE[site.id]
+
+ def clear_cache(self):
+ global _SITECONFIG_CACHE
+ _SITECONFIG_CACHE = {}
+
+ def check_expired(self):
+ """
+ Checks each cached SiteConfiguration to find out if its settings
+ have expired. This should be called on each request to ensure that
+ the copy of the settings is up-to-date in case another web server
+ worker process modifies the settings in the database.
+ """
+ global _SITECONFIG_CACHE
+
+ for key, siteconfig in _SITECONFIG_CACHE.copy().iteritems():
+ if siteconfig.is_expired():
+ # This is stale. Get rid of it so we can load it next time.
+ del _SITECONFIG_CACHE[key]
diff --git a/Pootle-2.0.0/external_apps/djblets/siteconfig/middleware.py b/Pootle-2.0.0/external_apps/djblets/siteconfig/middleware.py
new file mode 100644
index 0000000..0be851f
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/siteconfig/middleware.py
@@ -0,0 +1,13 @@
+from djblets.siteconfig.models import SiteConfiguration
+
+
+class SettingsMiddleware(object):
+ """
+ Middleware that performs necessary operations for siteconfig settings.
+
+ Right now, the primary responsibility is to check on each request if
+ the settings have expired, so that a web server worker process doesn't
+ end up with a stale view of the site settings.
+ """
+ def process_request(self, request):
+ SiteConfiguration.objects.check_expired()
diff --git a/Pootle-2.0.0/external_apps/djblets/siteconfig/models.py b/Pootle-2.0.0/external_apps/djblets/siteconfig/models.py
new file mode 100644
index 0000000..2d92d4a
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/siteconfig/models.py
@@ -0,0 +1,127 @@
+#
+# djblets/siteconfig/models.py
+#
+# Copyright (c) 2008 Christian Hammond
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+from datetime import datetime
+
+from django.contrib.sites.models import Site
+from django.core.cache import cache
+from django.db import models
+
+from djblets.siteconfig.managers import SiteConfigurationManager
+from djblets.util.fields import JSONField
+
+
+_DEFAULTS = {}
+
+
+class SiteConfiguration(models.Model):
+ """
+ Configuration data for a site. The version and all persistent settings
+ are stored here.
+
+ The usual way to retrieve a SiteConfiguration is to use
+ ```SiteConfiguration.objects.get_current()'''
+ """
+ site = models.ForeignKey(Site, related_name="config")
+ version = models.CharField(max_length=20)
+ settings = JSONField()
+
+ objects = SiteConfigurationManager()
+
+ def __init__(self, *args, **kwargs):
+ models.Model.__init__(self, *args, **kwargs)
+ self._last_sync_time = datetime.now()
+
+ def get(self, key, default=None):
+ """
+ Retrieves a setting. If the setting is not found, the default value
+ will be returned. This is represented by the default parameter, if
+ passed in, or a global default if set.
+ """
+ if default is None and self.id in _DEFAULTS:
+ default = _DEFAULTS[self.id].get(key, None)
+
+ return self.settings.get(key, default)
+
+ def set(self, key, value):
+ """
+ Sets a setting. The key should be a string, but the value can be
+ any native Python object.
+ """
+ self.settings[key] = value
+
+ def add_defaults(self, defaults_dict):
+ """
+ Adds a dictionary of defaults to this SiteConfiguration. These
+ defaults will be used when calling ```get''', if that setting wasn't
+ saved in the database.
+ """
+ if self.id not in _DEFAULTS:
+ _DEFAULTS[self.id] = {}
+
+ _DEFAULTS[self.id].update(defaults_dict)
+
+ def add_default(self, key, default_value):
+ """
+ Adds a single default setting.
+ """
+ self.add_defaults({key: default_value})
+
+ def get_defaults(self):
+ """
+ Returns all default settings registered with this SiteConfiguration.
+ """
+ if self.id not in _DEFAULTS:
+ _DEFAULTS[self.id] = {}
+
+ return _DEFAULTS[self.id]
+
+ def is_expired(self):
+ """
+ Returns whether or not this SiteConfiguration is expired and needs
+ to be reloaded.
+ """
+ last_updated = cache.get(self.__get_sync_cache_key())
+ return (isinstance(last_updated, datetime) and
+ last_updated > self._last_sync_time)
+
+ def save(self, **kwargs):
+ now = datetime.now()
+ self._last_sync_time = now
+ cache.set(self.__get_sync_cache_key(), now)
+
+ # The cached siteconfig might be stale now. We'll want a refresh.
+ # Also refresh the Site cache, since callers may get this from
+ # Site.config.
+ SiteConfiguration.objects.clear_cache()
+ Site.objects.clear_cache()
+
+ super(SiteConfiguration, self).save(**kwargs)
+
+ def __get_sync_cache_key(self):
+ return "%s:siteconfig:%s:last-updated" % (self.site.domain, self.id)
+
+ def __unicode__(self):
+ return "%s (version %s)" % (unicode(self.site), self.version)
diff --git a/Pootle-2.0.0/external_apps/djblets/siteconfig/views.py b/Pootle-2.0.0/external_apps/djblets/siteconfig/views.py
new file mode 100644
index 0000000..6340bfa
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/siteconfig/views.py
@@ -0,0 +1,58 @@
+#
+# djblets/siteconfig/views.py
+#
+# Copyright (c) 2008 Christian Hammond
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+from django.contrib.admin.views.decorators import staff_member_required
+from django.http import HttpResponseRedirect
+from django.shortcuts import render_to_response
+from django.template.context import RequestContext
+
+from djblets.siteconfig.models import SiteConfiguration
+
+
+@staff_member_required
+def site_settings(request, form_class,
+ template_name="siteconfig/settings.html",
+ extra_context={}):
+ """
+ Provides a front-end for customizing Review Board settings.
+ """
+ siteconfig = SiteConfiguration.objects.get_current()
+
+ if request.method == "POST":
+ form = form_class(siteconfig, request.POST, request.FILES)
+
+ if form.is_valid():
+ form.save()
+ return HttpResponseRedirect(".?saved=1")
+ else:
+ form = form_class(siteconfig)
+
+ context = {
+ 'form': form,
+ 'saved': request.GET.get('saved', 0)
+ }
+ context.update(extra_context)
+
+ return render_to_response(template_name, RequestContext(request, context))
diff --git a/Pootle-2.0.0/external_apps/djblets/util/__init__.py b/Pootle-2.0.0/external_apps/djblets/util/__init__.py
new file mode 100644
index 0000000..4ff70ff
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/util/__init__.py
@@ -0,0 +1,24 @@
+#
+# __init__.py - djblets.util top-level
+#
+# Copyright (c) 2007 David Trowbridge
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
diff --git a/Pootle-2.0.0/external_apps/djblets/util/context_processors.py b/Pootle-2.0.0/external_apps/djblets/util/context_processors.py
new file mode 100644
index 0000000..f8c694f
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/util/context_processors.py
@@ -0,0 +1,69 @@
+#
+# djblets/util/context_processors.py
+#
+# Copyright (c) 2007-2009 Christian Hammond
+# Copyright (c) 2007-2009 David Trowbridge
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+import os
+from datetime import datetime
+
+from django.conf import settings
+
+def settingsVars(request):
+ return {'settings': settings}
+
+
+def siteRoot(request):
+ """
+ Exposes a SITE_ROOT variable in templates. This assumes that the
+ project has been configured with a SITE_ROOT settings variable and
+ proper support for basing the installation in a subdirectory.
+ """
+ return {'SITE_ROOT': settings.SITE_ROOT}
+
+
+def mediaSerial(request):
+ """
+ Exposes a media serial number that can be appended to a media filename
+ in order to make a URL that can be cached forever without fear of change.
+ The next time the file is updated and the server is restarted, a new
+ path will be accessed and cached.
+
+ This returns the value of settings.MEDIA_SERIAL, which must either be
+ set manually or ideally should be set to the value of
+ djblets.util.misc.generate_media_serial().
+ """
+ return {'MEDIA_SERIAL': getattr(settings, "MEDIA_SERIAL", "")}
+
+
+def ajaxSerial(request):
+ """
+ Exposes a serial number that can be appended to filenames involving
+ dynamic loads of URLs in order to make a URL that can be cached forever
+ without fear of change.
+
+ This returns the value of settings.AJAX_SERIAL, which must either be
+ set manually or ideally should be set to the value of
+ djblets.util.misc.generate_ajax_serial().
+ """
+ return {'AJAX_SERIAL': getattr(settings, "AJAX_SERIAL", "")}
diff --git a/Pootle-2.0.0/external_apps/djblets/util/dates.py b/Pootle-2.0.0/external_apps/djblets/util/dates.py
new file mode 100644
index 0000000..4bc29ff
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/util/dates.py
@@ -0,0 +1,57 @@
+#
+# dates.py -- Date-related utilities.
+#
+# Copyright (c) 2008 Christian Hammond
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+from datetime import datetime
+import time
+
+from django.db.models import DateField
+
+
+def http_date(timestamp):
+ """
+ A wrapper around Django's http_date that accepts DateFields and
+ datetime objects directly.
+ """
+ from django.utils.http import http_date
+
+ if isinstance(timestamp, (DateField, datetime)):
+ return http_date(time.mktime(timestamp.timetuple()))
+ elif isinstance(timestamp, basestring):
+ return timestamp
+ else:
+ return http_date(timestamp)
+
+
+def get_latest_timestamp(timestamps):
+ """
+ Returns the latest timestamp in a list of timestamps.
+ """
+ latest = None
+
+ for timestamp in timestamps:
+ if latest is None or timestamp > latest:
+ latest = timestamp
+
+ return latest
diff --git a/Pootle-2.0.0/external_apps/djblets/util/db.py b/Pootle-2.0.0/external_apps/djblets/util/db.py
new file mode 100644
index 0000000..d86eed0
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/util/db.py
@@ -0,0 +1,51 @@
+#
+# db.py -- Database utilities.
+#
+# Copyright (c) 2007 David Trowbridge
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+
+from django.db import models, IntegrityError
+
+
+class ConcurrencyManager(models.Manager):
+ """
+ A class designed to work around database concurrency issues.
+ """
+ def get_or_create(self, **kwargs):
+ """
+ A wrapper around get_or_create that makes a final attempt to get
+ the object if the creation fails.
+
+ This helps with race conditions in the database where, between the
+ original get() and the create(), another process created the object,
+ causing us to fail. We'll then execute a get().
+
+ This is still prone to race conditions, but they're even more rare.
+ A delete() would have to happen before the unexpected create() but
+ before the get().
+ """
+ try:
+ return super(ConcurrencyManager, self).get_or_create(**kwargs)
+ except IntegrityError:
+ kwargs.pop('defaults', None)
+ return self.get(**kwargs)
diff --git a/Pootle-2.0.0/external_apps/djblets/util/dbevolution.py b/Pootle-2.0.0/external_apps/djblets/util/dbevolution.py
new file mode 100644
index 0000000..0cd2273
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/util/dbevolution.py
@@ -0,0 +1,33 @@
+from django_evolution.mutations import BaseMutation
+
+
+class FakeChangeFieldType(BaseMutation):
+ """
+ Changes the type of the field to a similar type.
+ This is intended only when the new type is really a version of the
+ old type, such as a subclass of that Field object. The two fields
+ should be compatible or there could be migration issues.
+ """
+ def __init__(self, model_name, field_name, new_type):
+ self.model_name = model_name
+ self.field_name = field_name
+ self.new_type = new_type
+
+ def __str__(self):
+ return "FakeChangeFieldType('%s', '%s', '%s')" % \
+ (self.model_name, self.field_name, self.new_type)
+
+ def simulate(self, app_label, proj_sig):
+ app_sig = proj_sig[app_label]
+ model_sig = app_sig[self.model_name]
+ field_dict = model_sig['fields']
+ field_sig = field_dict[self.field_name]
+
+ field_sig['field_type'] = self.new_type
+
+ def mutate(self, app_label, proj_sig):
+ # We can just call simulate, since it does the same thing.
+ # We're not actually generating SQL, but rather tricking
+ # Django Evolution.
+ self.simulate(app_label, proj_sig)
+ return ""
diff --git a/Pootle-2.0.0/external_apps/djblets/util/decorators.py b/Pootle-2.0.0/external_apps/djblets/util/decorators.py
new file mode 100644
index 0000000..87a0585
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/util/decorators.py
@@ -0,0 +1,177 @@
+#
+# decorators.py -- Miscellaneous, useful decorators. This might end up moving
+# to something with a different name.
+#
+# Copyright (c) 2007 David Trowbridge
+# Copyright (c) 2007 Christian Hammond
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+from inspect import getargspec
+
+from django import template
+from django.template import TemplateSyntaxError, Variable
+
+
+# The decorator decorator. This is copyright unknown, verbatim from
+# http://wiki.python.org/moin/PythonDecoratorLibrary
+def simple_decorator(decorator):
+ """This decorator can be used to turn simple functions
+ into well-behaved decorators, so long as the decorators
+ are fairly simple. If a decorator expects a function and
+ returns a function (no descriptors), and if it doesn't
+ modify function attributes or docstring, then it is
+ eligible to use this. Simply apply @simple_decorator to
+ your decorator and it will automatically preserve the
+ docstring and function attributes of functions to which
+ it is applied."""
+ def new_decorator(f):
+ g = decorator(f)
+ g.__name__ = f.__name__
+ g.__doc__ = f.__doc__
+ g.__dict__.update(f.__dict__)
+ return g
+ # Now a few lines needed to make simple_decorator itself
+ # be a well-behaved decorator.
+ new_decorator.__name__ = decorator.__name__
+ new_decorator.__doc__ = decorator.__doc__
+ new_decorator.__dict__.update(decorator.__dict__)
+ return new_decorator
+
+
+def basictag(takes_context=False):
+ """
+ A decorator similar to Django's @register.simple_tag that optionally
+ takes a context parameter. This condenses many tag implementations down
+ to a few lines of code.
+
+ Example:
+ @register.tag
+ @basictag(takes_context=True)
+ def printuser(context):
+ return context['user']
+ """
+ class BasicTagNode(template.Node):
+ def __init__(self, take_context, tag_name, tag_func, args):
+ self.takes_context = takes_context
+ self.tag_name = tag_name
+ self.tag_func = tag_func
+ self.args = args
+
+ def render(self, context):
+ args = [Variable(var).resolve(context) for var in self.args]
+
+ if self.takes_context:
+ return self.tag_func(context, *args)
+ else:
+ return self.tag_func(*args)
+
+ def basictag_func(tag_func):
+ def _setup_tag(parser, token):
+ bits = token.split_contents()
+ tag_name = bits[0]
+ del(bits[0])
+
+ params, xx, xxx, defaults = getargspec(tag_func)
+ max_args = len(params)
+
+ if takes_context:
+ if params[0] == 'context':
+ max_args -= 1 # Ignore context
+ else:
+ raise TemplateSyntaxError, \
+ "Any tag function decorated with takes_context=True " \
+ "must have a first argument of 'context'"
+
+ min_args = max_args - len(defaults or [])
+
+ if not min_args <= len(bits) <= max_args:
+ if min_args == max_args:
+ raise TemplateSyntaxError, \
+ "%r tag takes %d arguments." % (tag_name, min_args)
+ else:
+ raise TemplateSyntaxError, \
+ "%r tag takes %d to %d arguments, got %d." % \
+ (tag_name, min_args, max_args, len(bits))
+
+ return BasicTagNode(takes_context, tag_name, tag_func, bits)
+
+ _setup_tag.__name__ = tag_func.__name__
+ _setup_tag.__doc__ = tag_func.__doc__
+ _setup_tag.__dict__.update(tag_func.__dict__)
+ return _setup_tag
+
+ return basictag_func
+
+
+def blocktag(tag_func):
+ """
+ A decorator similar to Django's @register.simple_tag that does all the
+ redundant work of parsing arguments and creating a node class in order
+ to render content between a foo and endfoo tag block. This condenses
+ many tag implementations down to a few lines of code.
+
+ Example:
+ @register.tag
+ @blocktag
+ def divify(context, nodelist, div_id=None):
+ s = "<div"
+ if div_id:
+ s += " id='%s'" % div_id
+ return s + ">" + nodelist.render(context) + "</div>"
+ """
+ class BlockTagNode(template.Node):
+ def __init__(self, tag_name, tag_func, nodelist, args):
+ self.tag_name = tag_name
+ self.tag_func = tag_func
+ self.nodelist = nodelist
+ self.args = args
+
+ def render(self, context):
+ args = [Variable(var).resolve(context) for var in self.args]
+ return self.tag_func(context, self.nodelist, *args)
+
+ def _setup_tag(parser, token):
+ bits = token.split_contents()
+ tag_name = bits[0]
+ del(bits[0])
+
+ params, xx, xxx, defaults = getargspec(tag_func)
+ max_args = len(params) - 2 # Ignore context and nodelist
+ min_args = max_args - len(defaults or [])
+
+ if not min_args <= len(bits) <= max_args:
+ if min_args == max_args:
+ raise TemplateSyntaxError, \
+ "%r tag takes %d arguments." % (tag_name, min_args)
+ else:
+ raise TemplateSyntaxError, \
+ "%r tag takes %d to %d arguments, got %d." % \
+ (tag_name, min_args, max_args, len(bits))
+
+ nodelist = parser.parse(('end%s' % tag_name),)
+ parser.delete_first_token()
+ return BlockTagNode(tag_name, tag_func, nodelist, bits)
+
+ _setup_tag.__name__ = tag_func.__name__
+ _setup_tag.__doc__ = tag_func.__doc__
+ _setup_tag.__dict__.update(tag_func.__dict__)
+ return _setup_tag
diff --git a/Pootle-2.0.0/external_apps/djblets/util/fields.py b/Pootle-2.0.0/external_apps/djblets/util/fields.py
new file mode 100644
index 0000000..5d31111
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/util/fields.py
@@ -0,0 +1,208 @@
+#
+# fields.py -- Model fields.
+#
+# Copyright (c) 2007-2008 Christian Hammond
+# Copyright (c) 2007-2008 David Trowbridge
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import base64
+import logging
+from datetime import datetime
+
+from django.conf import settings
+from django.core.serializers.json import DjangoJSONEncoder
+from django.db import models
+from django.utils import simplejson
+from django.utils.encoding import smart_unicode
+
+
+class Base64DecodedValue(str):
+ """
+ A subclass of string that can be identified by Base64Field, in order
+ to prevent double-encoding or double-decoding.
+ """
+ pass
+
+
+class Base64FieldCreator(object):
+ def __init__(self, field):
+ self.field = field
+
+ def __set__(self, obj, value):
+ pk_val = obj._get_pk_val(obj.__class__._meta)
+ pk_set = pk_val is not None and smart_unicode(pk_val) != u''
+
+ if (isinstance(value, Base64DecodedValue) or not pk_set):
+ obj.__dict__[self.field.name] = base64.encodestring(value)
+ else:
+ obj.__dict__[self.field.name] = value
+
+ setattr(obj, "%s_initted" % self.field.name, True)
+
+ def __get__(self, obj, type=None):
+ if obj is None:
+ raise AttributeError('Can only be accessed via an instance.')
+
+ value = obj.__dict__[self.field.name]
+
+ if value is None:
+ return None
+ else:
+ return Base64DecodedValue(base64.decodestring(value))
+
+
+class Base64Field(models.TextField):
+ """
+ A subclass of TextField that encodes its data as base64 in the database.
+ This is useful if you're dealing with unknown encodings and must guarantee
+ that no modifications to the text occurs and that you can read/write
+ the data in any database with any encoding.
+ """
+ serialize_to_string = True
+
+ def contribute_to_class(self, cls, name):
+ super(Base64Field, self).contribute_to_class(cls, name)
+ setattr(cls, self.name, Base64FieldCreator(self))
+
+ def get_db_prep_value(self, value):
+ if isinstance(value, Base64DecodedValue):
+ value = base64.encodestring(value)
+
+ return value
+
+ def save_form_data(self, instance, data):
+ setattr(instance, self.name, Base64DecodedValue(data))
+
+ def to_python(self, value):
+ if isinstance(value, Base64DecodedValue):
+ return value
+ else:
+ return Base64DecodedValue(base64.decodestring(value))
+
+ def value_to_string(self, obj):
+ value = self._get_val_from_obj(obj)
+
+ if isinstance(value, Base64DecodedValue):
+ return base64.encodestring(value)
+ else:
+ return value
+
+
+class ModificationTimestampField(models.DateTimeField):
+ """
+ A subclass of DateTimeField that only auto-updates the timestamp when
+ updating an existing object or when the value of the field is None. This
+ specialized field is equivalent to DateTimeField's auto_now=True, except
+ it allows for custom timestamp values (needed for
+ serialization/deserialization).
+ """
+ def __init__(self, verbose_name=None, name=None, **kwargs):
+ kwargs.update({
+ 'editable': False,
+ 'blank': True,
+ })
+ models.DateTimeField.__init__(self, verbose_name, name, **kwargs)
+
+ def pre_save(self, model, add):
+ if not add or getattr(model, self.attname) is None:
+ value = datetime.now()
+ setattr(model, self.attname, value)
+ return value
+
+ return super(ModificationTimestampField, self).pre_save(model, add)
+
+ def get_internal_type(self):
+ return "DateTimeField"
+
+
+class JSONField(models.TextField):
+ """
+ A field for storing JSON-encoded data. The data is accessible as standard
+ Python data types and is transparently encoded/decoded to/from a JSON
+ string in the database.
+ """
+ serialize_to_string = True
+
+ def __init__(self, verbose_name=None, name=None,
+ encoder=DjangoJSONEncoder(), **kwargs):
+ models.TextField.__init__(self, verbose_name, name, blank=True,
+ **kwargs)
+ self.encoder = encoder
+
+ def db_type(self):
+ return "text"
+
+ def contribute_to_class(self, cls, name):
+ def get_json(model_instance):
+ return self.dumps(getattr(model_instance, self.attname, None))
+
+ def set_json(model_instance, json):
+ setattr(model_instance, self.attname, self.loads(json))
+
+ super(JSONField, self).contribute_to_class(cls, name)
+
+ setattr(cls, "get_%s_json" % self.name, get_json)
+ setattr(cls, "set_%s_json" % self.name, set_json)
+
+ models.signals.post_init.connect(self.post_init, sender=cls)
+
+ def pre_save(self, model_instance, add):
+ return self.dumps(getattr(model_instance, self.attname, None))
+
+ def post_init(self, instance=None, **kwargs):
+ value = self.value_from_object(instance)
+
+ if value:
+ value = self.loads(value)
+ else:
+ value = {}
+
+ setattr(instance, self.attname, value)
+
+ def get_db_prep_save(self, value):
+ if not isinstance(value, basestring):
+ value = self.dumps(value)
+
+ return super(JSONField, self).get_db_prep_save(value)
+
+ def value_to_string(self, obj):
+ return self.dumps(self.value_from_object(obj))
+
+ def dumps(self, data):
+ return self.encoder.encode(data)
+
+ def loads(self, val):
+ try:
+ val = simplejson.loads(val, encoding=settings.DEFAULT_CHARSET)
+
+ # XXX We need to investigate why this is happening once we have
+ # a solid repro case.
+ if isinstance(val, basestring):
+ logging.warning("JSONField decode error. Expected dictionary, "
+ "got string for input '%s'" % val)
+ # For whatever reason, we may have gotten back
+ val = simplejson.loads(val, encoding=settings.DEFAULT_CHARSET)
+ except ValueError:
+ # There's probably embedded unicode markers (like u'foo') in the
+ # string. We have to eval it.
+ val = eval(val)
+
+ return val
diff --git a/Pootle-2.0.0/external_apps/djblets/util/http.py b/Pootle-2.0.0/external_apps/djblets/util/http.py
new file mode 100644
index 0000000..2fe9a38
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/util/http.py
@@ -0,0 +1,80 @@
+#
+# http.py -- HTTP-related utilities.
+#
+# Copyright (c) 2008 Christian Hammond
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+from djblets.util.dates import http_date
+
+
+def set_last_modified(response, timestamp):
+ """
+ Sets the Last-Modified header in a response based on a DateTimeField.
+ """
+ response['Last-Modified'] = http_date(timestamp)
+
+
+def get_modified_since(request, last_modified):
+ """
+ Checks if a Last-Modified timestamp is newer than the requested
+ HTTP_IF_MODIFIED_SINCE from the browser. This can be used to bail
+ early if no updates have been performed since the last access to the
+ page.
+
+ This can take a DateField, datetime, HTTP date-formatted string, or
+ a function for the last_modified timestamp. If a function is passed,
+ it will only be called if the HTTP_IF_MODIFIED_SINCE header is present.
+ """
+ if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE', None)
+
+ if if_modified_since is not None:
+ if callable(last_modified):
+ last_modified = last_modified()
+
+ return (if_modified_since == http_date(last_modified))
+
+ return False
+
+
+def set_etag(response, etag):
+ """
+ Sets the ETag header in a response.
+ """
+ response['ETag'] = etag
+
+
+def etag_if_none_match(request, etag):
+ """
+ Checks if an ETag matches the If-None-Match header sent by the browser.
+ This can be used to bail early if no updates have been performed since
+ the last access to the page.
+ """
+ return etag == request.META.get('If-None-Match', None)
+
+
+def etag_if_match(request, etag):
+ """
+ Checks if an ETag matches the If-Match header sent by the browser. This
+ is used by PUT requests to to indicate that the update should only happen
+ if the specified ETag matches the header.
+ """
+ return etag == request.META.get('If-Match', None)
diff --git a/Pootle-2.0.0/external_apps/djblets/util/misc.py b/Pootle-2.0.0/external_apps/djblets/util/misc.py
new file mode 100644
index 0000000..a5ad85e
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/util/misc.py
@@ -0,0 +1,269 @@
+#
+# misc.py -- Miscellaneous utilities.
+#
+# Copyright (c) 2007 David Trowbridge
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+
+import logging
+import os
+import zlib
+
+try:
+ import cPickle as pickle
+except ImportError:
+ import pickle
+
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+from django.core.cache import cache
+from django.conf import settings
+from django.conf.urls.defaults import url, RegexURLPattern
+from django.contrib.sites.models import Site
+from django.db.models.manager import Manager
+from django.views.decorators.cache import never_cache
+
+
+DEFAULT_EXPIRATION_TIME = 60 * 60 * 24 * 30 # 1 month
+CACHE_CHUNK_SIZE = 2**20 - 1024 # almost 1M (memcached's slab limit)
+
+
+class MissingChunkError(Exception):
+ pass
+
+
+def _cache_fetch_large_data(cache, key):
+ chunk_count = cache.get(key)
+ data = []
+
+ chunk_keys = ['%s-%d' % (key, i) for i in range(int(chunk_count))]
+ chunks = cache.get_many(chunk_keys)
+ for chunk_key in chunk_keys:
+ try:
+ data.append(chunks[chunk_key][0])
+ except KeyError:
+ logging.info('Cache miss for key %s.' % chunk_key)
+ raise MissingChunkError
+
+ data = ''.join(data)
+
+ data = zlib.decompress(data)
+ try:
+ unpickler = pickle.Unpickler(StringIO(data))
+ data = unpickler.load()
+ except Exception, e:
+ logging.warning("Unpickle error for cache key %s: %s." % (key, e))
+ raise e
+
+ return data
+
+
+def _cache_store_large_data(cache, key, data, expiration):
+ # We store large data in the cache broken into chunks that are 1M in size.
+ # To do this easily, we first pickle the data and compress it with zlib.
+ # This gives us a string which can be chunked easily. These are then stored
+ # individually in the cache as single-element lists (so the cache backend
+ # doesn't try to convert binary data to utf8). The number of chunks needed
+ # is stored in the cache under the unadorned key
+ file = StringIO()
+ pickler = pickle.Pickler(file)
+ pickler.dump(data)
+ data = file.getvalue()
+ data = zlib.compress(data)
+
+ i = 0
+ while len(data) > CACHE_CHUNK_SIZE:
+ chunk = data[0:CACHE_CHUNK_SIZE]
+ data = data[CACHE_CHUNK_SIZE:]
+ cache.set('%s-%d' % (key, i), chunk, expiration)
+ i += 1
+ cache.set('%s-%d' % (key, i), [data], expiration)
+
+ cache.set(key, '%d' % (i + 1), expiration)
+
+
+def cache_memoize(key, lookup_callable,
+ expiration=getattr(settings, "CACHE_EXPIRATION_TIME",
+ DEFAULT_EXPIRATION_TIME),
+ force_overwrite=False,
+ large_data=False):
+ """Memoize the results of a callable inside the configured cache.
+
+ Keyword arguments:
+ expiration -- The expiration time for the key.
+ force_overwrite -- If True, the value will always be computed and stored
+ regardless of whether it exists in the cache already.
+ large_data -- If True, the resulting data will be pickled, gzipped,
+ and (potentially) split up into megabyte-sized chunks.
+ This is useful for very large, computationally
+ intensive hunks of data which we don't want to store
+ in a database due to the way things are accessed.
+ """
+ try:
+ site = Site.objects.get_current()
+
+ # The install has a Site app, so prefix the domain to the key.
+ key = "%s:%s" % (site.domain, key)
+ except:
+ # The install doesn't have a Site app, so use the key as-is.
+ pass
+
+ if large_data:
+ if not force_overwrite and cache.has_key(key):
+ try:
+ data = _cache_fetch_large_data(cache, key)
+ return data
+ except Exception, e:
+ logging.warning('Failed to fetch large data from cache for key %s: %s.' % (key, e))
+ else:
+ logging.info('Cache miss for key %s.' % key)
+
+ data = lookup_callable()
+ _cache_store_large_data(cache, key, data, expiration)
+ return data
+
+ else:
+ if not force_overwrite and cache.has_key(key):
+ return cache.get(key)
+ data = lookup_callable()
+
+ # Most people will be using memcached, and memcached has a limit of 1MB.
+ # Data this big should be broken up somehow, so let's warn about this.
+ # Users should hopefully be using large_data=True in this case.
+ # XXX - since 'data' may be a sequence that's not a string/unicode,
+ # this can fail. len(data) might be something like '6' but the
+ # data could exceed a megabyte. The best way to catch this would
+ # be an exception, but while python-memcached defines an exception
+ # type for this, it never uses it, choosing instead to fail
+ # silently. WTF.
+ if len(data) >= CACHE_CHUNK_SIZE:
+ logging.warning("Cache data for key %s (length %s) may be too big "
+ "for the cache." % (key, len(data)))
+
+ try:
+ cache.set(key, data, expiration)
+ except:
+ pass
+ return data
+
+
+def get_object_or_none(klass, *args, **kwargs):
+ if isinstance(klass, Manager):
+ manager = klass
+ klass = manager.model
+ else:
+ manager = klass._default_manager
+
+ try:
+ return manager.get(*args, **kwargs)
+ except klass.DoesNotExist:
+ return None
+
+
+def never_cache_patterns(prefix, *args):
+ """
+ Prevents any included URLs from being cached by the browser.
+
+ It's sometimes desirable not to allow browser caching for a set of URLs.
+ This can be used just like patterns().
+ """
+ pattern_list = []
+ for t in args:
+ if isinstance(t, (list, tuple)):
+ t = url(prefix=prefix, *t)
+ elif isinstance(t, RegexURLPattern):
+ t.add_prefix(prefix)
+
+ t._callback = never_cache(t.callback)
+ pattern_list.append(t)
+
+ return pattern_list
+
+
+
+def generate_media_serial():
+ """
+ Generates a media serial number that can be appended to a media filename
+ in order to make a URL that can be cached forever without fear of change.
+ The next time the file is updated and the server is restarted, a new
+ path will be accessed and cached.
+
+ This will crawl the media files (using directories in MEDIA_SERIAL_DIRS if
+ specified, or all of MEDIA_ROOT otherwise), figuring out the latest
+ timestamp, and return that value.
+ """
+ MEDIA_SERIAL = getattr(settings, "MEDIA_SERIAL", 0)
+
+ if not MEDIA_SERIAL:
+ media_dirs = getattr(settings, "MEDIA_SERIAL_DIRS", ["."])
+
+ for media_dir in media_dirs:
+ media_path = os.path.join(settings.MEDIA_ROOT, media_dir)
+
+ for root, dirs, files in os.walk(media_path):
+ for name in files:
+ mtime = int(os.stat(os.path.join(root, name)).st_mtime)
+
+ if mtime > MEDIA_SERIAL:
+ MEDIA_SERIAL = mtime
+
+ setattr(settings, "MEDIA_SERIAL", MEDIA_SERIAL)
+
+
+def generate_ajax_serial():
+ """
+ Generates a serial number that can be appended to filenames involving
+ dynamic loads of URLs in order to make a URL that can be cached forever
+ without fear of change.
+
+ This will crawl the template files (using directories in TEMPLATE_DIRS),
+ figuring out the latest timestamp, and return that value.
+ """
+ AJAX_SERIAL = getattr(settings, "AJAX_SERIAL", 0)
+
+ if not AJAX_SERIAL:
+ template_dirs = getattr(settings, "TEMPLATE_DIRS", ["."])
+
+ for template_path in template_dirs:
+ for root, dirs, files in os.walk(template_path):
+ for name in files:
+ mtime = int(os.stat(os.path.join(root, name)).st_mtime)
+
+ if mtime > AJAX_SERIAL:
+ AJAX_SERIAL = mtime
+
+ setattr(settings, "AJAX_SERIAL", AJAX_SERIAL)
+
+
+def generate_cache_serials():
+ """
+ Wrapper around generate_media_serial and generate_ajax_serial to
+ generate all serial numbers in one go.
+
+ This should be called early in the startup, such as in the site's
+ main urls.py.
+ """
+ generate_media_serial()
+ generate_ajax_serial()
diff --git a/Pootle-2.0.0/external_apps/djblets/util/rooturl.py b/Pootle-2.0.0/external_apps/djblets/util/rooturl.py
new file mode 100644
index 0000000..365b709
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/util/rooturl.py
@@ -0,0 +1,15 @@
+from django.conf import settings
+from django.conf.urls.defaults import patterns, include, handler404, handler500
+from django.core.exceptions import ImproperlyConfigured
+
+
+# Ensures that we can run nose on this without needing to set SITE_ROOT.
+# Also serves to let people know if they set one variable without the other.
+if hasattr(settings, "SITE_ROOT"):
+ if not hasattr(settings, "SITE_ROOT_URLCONF"):
+ raise ImproperlyConfigured("SITE_ROOT_URLCONF must be set when "
+ "using SITE_ROOT")
+
+ urlpatterns = patterns('',
+ (r'^%s' % settings.SITE_ROOT[1:], include(settings.SITE_ROOT_URLCONF)),
+ )
diff --git a/Pootle-2.0.0/external_apps/djblets/util/templatetags/__init__.py b/Pootle-2.0.0/external_apps/djblets/util/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/util/templatetags/__init__.py
diff --git a/Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_deco.py b/Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_deco.py
new file mode 100644
index 0000000..bfcc258
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_deco.py
@@ -0,0 +1,57 @@
+#
+# djblets_deco.py -- Decorational template tags
+#
+# Copyright (c) 2007-2008 Christian Hammond
+# Copyright (c) 2007-2008 David Trowbridge
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+from django import template
+from django.template.loader import render_to_string
+
+from djblets.util.decorators import blocktag
+
+
+register = template.Library()
+
+
+@register.tag
+@blocktag
+def box(context, nodelist, classname=None):
+ """
+ Displays a box container around content, with an optional class name.
+ """
+ return render_to_string('deco/box.html', {
+ 'classname': classname or "",
+ 'content': nodelist.render(context)
+ })
+
+
+@register.tag
+@blocktag
+def errorbox(context, nodelist, box_id=None):
+ """
+ Displays an error box around content, with an optional ID.
+ """
+ return render_to_string('deco/errorbox.html', {
+ 'box_id': box_id or "",
+ 'content': nodelist.render(context)
+ })
diff --git a/Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_email.py b/Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_email.py
new file mode 100644
index 0000000..02d1b75
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_email.py
@@ -0,0 +1,71 @@
+#
+# djblets_email.py -- E-mail formatting template tags
+#
+# Copyright (c) 2007-2008 Christian Hammond
+# Copyright (c) 2007-2008 David Trowbridge
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+import re
+
+from django import template
+from django.template.loader import render_to_string
+
+from djblets.util.decorators import basictag, blocktag
+
+
+register = template.Library()
+
+
+@register.tag
+@basictag(takes_context=True)
+def quoted_email(context, template_name):
+ """
+ Renders a specified template as a quoted reply, using the current context.
+ """
+ return quote_text(render_to_string(template_name, context))
+
+
+@register.tag
+@blocktag
+def condense(context, nodelist):
+ """
+ Condenses a block of text so that there are never more than three
+ consecutive newlines.
+ """
+ text = nodelist.render(context).strip()
+ text = re.sub("\n{4,}", "\n\n\n", text)
+ return text
+
+
+@register.filter
+def quote_text(text, level=1):
+ """
+ Quotes a block of text the specified number of times.
+ """
+ lines = text.split("\n")
+ quoted = ""
+
+ for line in lines:
+ quoted += "%s%s\n" % ("> " * level, line)
+
+ return quoted.rstrip()
+
diff --git a/Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_forms.py b/Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_forms.py
new file mode 100644
index 0000000..694bac3
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_forms.py
@@ -0,0 +1,95 @@
+#
+# djblets_forms.py -- Form-related template tags
+#
+# Copyright (c) 2008 Christian Hammond
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+from django import template
+from django.forms import BooleanField
+
+
+register = template.Library()
+
+
+@register.simple_tag
+def label_tag(field):
+ """
+ Outputs the tag for a field's label. This gives more fine-grained
+ control over the appearance of the form.
+
+ This exists because a template can't access this directly from a field
+ in newforms.
+ """
+ is_checkbox = is_field_checkbox(field)
+
+ s = '<label for="%s"' % form_field_id(field)
+
+ classes = []
+
+ if field.field.required:
+ classes.append("required")
+
+ if is_checkbox:
+ classes.append("vCheckboxLabel")
+
+ if classes:
+ s += ' class="%s"' % " ".join(classes)
+
+ s += '>%s' % field.label
+
+ if not is_checkbox:
+ s += ':'
+
+ s += '</label>'
+
+ return s
+
+
+@register.filter
+def form_field_id(field):
+ """
+ Outputs the ID of a field.
+ """
+ widget = field.field.widget
+ id_ = widget.attrs.get('id') or field.auto_id
+
+ if id_:
+ return widget.id_for_label(id_)
+
+ return ""
+
+
+@register.filter
+def is_field_checkbox(field):
+ """
+ Returns whether or not this field is a checkbox (a ```BooleanField''').
+ """
+ return isinstance(field.field, BooleanField)
+
+
+@register.filter
+def form_field_has_label_first(field):
+ """
+ Returns whether or not this field should display the label before the
+ widget. This is the case in all fields except checkboxes.
+ """
+ return not is_field_checkbox(field)
diff --git a/Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_images.py b/Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_images.py
new file mode 100644
index 0000000..da30211
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_images.py
@@ -0,0 +1,100 @@
+#
+# djblets_images.py -- Image-related template tags
+#
+# Copyright (c) 2007-2008 Christian Hammond
+# Copyright (c) 2007-2008 David Trowbridge
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+import os
+
+try:
+ from PIL import Image
+except ImportError:
+ import Image
+
+from django import template
+from django.conf import settings
+
+
+register = template.Library()
+
+
+@register.simple_tag
+def crop_image(file, x, y, width, height):
+ """
+ Crops an image at the specified coordinates and dimensions, returning the
+ resulting URL of the cropped image.
+ """
+ filename = file.name
+
+ if filename.find(".") != -1:
+ basename, format = filename.rsplit('.', 1)
+ new_name = '%s_%s_%s_%s_%s.%s' % (basename, x, y, width, height, format)
+ else:
+ basename = filename
+ new_name = '%s_%s_%s_%s_%s' % (basename, x, y, width, height)
+
+ new_path = os.path.join(settings.MEDIA_ROOT, new_name)
+ new_url = os.path.join(settings.MEDIA_URL, new_name)
+
+ if not os.path.exists(new_path):
+ try:
+ image = Image.open(os.path.join(settings.MEDIA_ROOT, filename))
+ image = image.crop((x, y, x + width, y + height))
+ image.save(new_path, image.format)
+ except (IOError, KeyError):
+ return ""
+
+ return new_url
+
+
+# From http://www.djangosnippets.org/snippets/192
+@register.filter
+def thumbnail(file, size='400x100'):
+ """
+ Creates a thumbnail of an image with the specified size, returning
+ the URL of the thumbnail.
+ """
+ x, y = [int(x) for x in size.split('x')]
+
+ filename = file.name
+ if filename.find(".") != -1:
+ basename, format = filename.rsplit('.', 1)
+ miniature = '%s_%s.%s' % (basename, size, format)
+ else:
+ basename = filename
+ miniature = '%s_%s' % (basename, size)
+
+ miniature_filename = os.path.join(settings.MEDIA_ROOT, miniature)
+ miniature_url = os.path.join(settings.MEDIA_URL, miniature)
+
+ if not os.path.exists(miniature_filename):
+ try:
+ image = Image.open(os.path.join(settings.MEDIA_ROOT, filename))
+ image.thumbnail([x, y], Image.ANTIALIAS)
+ image.save(miniature_filename, image.format)
+ except IOError:
+ return ""
+ except KeyError:
+ return ""
+
+ return miniature_url
diff --git a/Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_js.py b/Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_js.py
new file mode 100644
index 0000000..2452373
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_js.py
@@ -0,0 +1,59 @@
+#
+# djblets_js.py -- JavaScript-related template tags
+#
+# Copyright (c) 2007-2008 Christian Hammond
+# Copyright (c) 2007-2008 David Trowbridge
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+from django import template
+
+
+register = template.Library()
+
+
+@register.simple_tag
+def form_dialog_fields(form):
+ """
+ Translates a Django Form object into a JavaScript list of fields.
+ The resulting list of fields can be used to represent the form
+ dynamically.
+ """
+ s = ''
+
+ for field in form:
+ s += "{ name: '%s', " % field.name
+
+ if field.is_hidden:
+ s += "hidden: true, "
+ else:
+ s += "label: '%s', " % field.label_tag(field.label + ":")
+
+ if field.field.required:
+ s += "required: true, "
+
+ if field.field.help_text:
+ s += "help_text: '%s', " % field.field.help_text
+
+ s += "widget: '%s' }," % unicode(field)
+
+ # Chop off the last ','
+ return "[ %s ]" % s[:-1]
diff --git a/Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_utils.py b/Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_utils.py
new file mode 100644
index 0000000..c09092a
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/util/templatetags/djblets_utils.py
@@ -0,0 +1,283 @@
+#
+# djblets_utils.py -- Various utility template tags
+#
+# Copyright (c) 2007-2008 Christian Hammond
+# Copyright (c) 2007-2008 David Trowbridge
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+import datetime
+import os
+
+from django import template
+from django.template import TemplateSyntaxError
+from django.template.defaultfilters import stringfilter
+from django.template.loader import render_to_string
+from django.utils.safestring import mark_safe
+
+from djblets.util.decorators import basictag, blocktag
+
+
+register = template.Library()
+
+
+@register.tag
+@blocktag
+def definevar(context, nodelist, varname):
+ """
+ Defines a variable in the context based on the contents of the block.
+ This is useful when you need to reuse the results of some tag logic
+ multiple times in a template or in a blocktrans tag.
+ """
+ context[varname] = nodelist.render(context)
+ return ""
+
+
+@register.tag
+@blocktag
+def ifuserorperm(context, nodelist, user, perm):
+ """
+ Renders content depending on whether the logged in user is the specified
+ user or has the specified permission.
+
+ This is useful when you want to restrict some code to the owner of a
+ review request or to a privileged user that has the abilities of the
+ owner.
+
+ Example::
+
+ {% ifuserorperm myobject.user "myobject.can_change_status" %}
+ Owner-specific content here...
+ {% endifuserorperm %}
+ """
+ req_user = context.get('user', None)
+ if user == req_user or req_user.has_perm(perm):
+ return nodelist.render(context)
+
+ return ''
+
+
+@register.tag
+@basictag(takes_context=True)
+def include_as_string(context, template_name):
+ s = render_to_string(template_name, context)
+ s = s.replace("'", "\\'")
+ s = s.replace("\n", "\\\n")
+ return "'%s'" % s
+
+
+@register.tag
+@blocktag
+def attr(context, nodelist, attrname):
+ """
+ Sets an HTML attribute to a value if the value is not an empty string.
+ """
+ content = nodelist.render(context)
+
+ if content.strip() == "":
+ return ""
+
+ return ' %s="%s"' % (attrname, content)
+
+
+@register.filter
+def escapespaces(value):
+ """
+ HTML-escapes all spaces with ``&nbsp;`` and newlines with ``<br />``.
+ """
+ return value.replace(' ', '&nbsp; ').replace('\n', '<br />')
+
+
+@register.simple_tag
+def ageid(timestamp):
+ """
+ Returns an ID based on the difference between a timestamp and the
+ current time.
+
+ The ID is returned based on the following differences in days:
+
+ ========== ====
+ Difference ID
+ ========== ====
+ 0 age1
+ 1 age2
+ 2 age3
+ 3 age4
+ 4 or more age5
+ ========== ====
+ """
+ if timestamp is None:
+ return ""
+
+ # Convert datetime.date into datetime.datetime
+ if timestamp.__class__ is not datetime.datetime:
+ timestamp = datetime.datetime(timestamp.year, timestamp.month,
+ timestamp.day)
+
+
+ now = datetime.datetime.now()
+ delta = now - (timestamp -
+ datetime.timedelta(0, 0, timestamp.microsecond))
+
+ if delta.days == 0:
+ return "age1"
+ elif delta.days == 1:
+ return "age2"
+ elif delta.days == 2:
+ return "age3"
+ elif delta.days == 3:
+ return "age4"
+ else:
+ return "age5"
+
+
+@register.filter
+def user_displayname(user):
+ """
+ Returns the display name of the user.
+
+ If the user has a full name set, it will display this. Otherwise, it will
+ display the username.
+ """
+ return user.get_full_name() or user.username
+
+
+@register.filter
+def humanize_list(value):
+ """
+ Humanizes a list of values, inserting commands and "and" where appropriate.
+
+ ========================= ======================
+ Example List Resulting string
+ ========================= ======================
+ ``["a"]`` ``"a"``
+ ``["a", "b"]`` ``"a and b"``
+ ``["a", "b", "c"]`` ``"a, b and c"``
+ ``["a", "b", "c", "d"]`` ``"a, b, c, and d"``
+ ========================= ======================
+ """
+ if len(value) == 0:
+ return ""
+ elif len(value) == 1:
+ return value[0]
+
+ s = ", ".join(value[:-1])
+
+ if len(value) > 3:
+ s += ","
+
+ return "%s and %s" % (s, value[-1])
+
+
+@register.filter
+def contains(container, value):
+ """
+ Returns True if the specified value is in the specified container.
+ """
+ return value in container
+
+
+@register.filter
+def getitem(container, value):
+ """
+ Returns the attribute of a specified name from a container.
+ """
+ return container[value]
+
+
+@register.filter
+def exclude_item(container, item):
+ """
+ Excludes an item from a list.
+ """
+ if isinstance(container, list):
+ container = list(container)
+
+ if item in container:
+ container.remove(item)
+ else:
+ raise TemplateSyntaxError, "remove_item expects a list"
+
+ return container
+
+
+@register.filter
+def indent(value, numspaces=4):
+ """
+ Indents a string by the specified number of spaces.
+ """
+ indent_str = " " * numspaces
+ return indent_str + value.replace("\n", "\n" + indent_str)
+
+
+@register.filter
+def basename(value):
+ """
+ Returns the basename of a path.
+ """
+ return os.path.basename(value)
+
+
+@register.filter(name="range")
+def range_filter(value):
+ """
+ Turns an integer into a range of numbers.
+
+ This is useful for iterating with the "for" tag. For example:
+
+ {% for i in 10|range %}
+ {{i}}
+ {% endfor %}
+ """
+ return range(value)
+
+
+@register.filter
+def realname(user):
+ """
+ Returns the real name of a user, if available, or the username.
+
+ If the user has a full name set, this will return the full name.
+ Otherwise, this returns the username.
+ """
+ full_name = user.get_full_name()
+ if full_name == '':
+ return user.username
+ else:
+ return full_name
+
+
+@register.filter
+@stringfilter
+def paragraphs(text):
+ """
+ Adds <p>...</p> tags around blocks of text in a string. This expects
+ that each paragraph in the string will be on its own line. Blank lines
+ are filtered out.
+ """
+ s = ""
+
+ for line in text.splitlines():
+ if line:
+ s += "<p>%s</p>\n" % line
+
+ return mark_safe(s)
+paragraphs.is_safe = True
diff --git a/Pootle-2.0.0/external_apps/djblets/util/testing.py b/Pootle-2.0.0/external_apps/djblets/util/testing.py
new file mode 100644
index 0000000..53cfdf0
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/util/testing.py
@@ -0,0 +1,57 @@
+#
+# djblets/util/testing.py - Some classes useful for unit testing django-based
+# applications
+#
+# Copyright (c) 2007 David Trowbridge
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+from django.template import Node
+from django.test import TestCase
+
+
+class StubNodeList(Node):
+ def __init__(self, default_text):
+ self.default_text = default_text
+
+ def render(self, context):
+ return self.default_text
+
+
+class StubParser:
+ def __init__(self, default_text):
+ self.default_text = default_text
+
+ def parse(self, until):
+ return StubNodeList(self.default_text)
+
+ def delete_first_token(self):
+ pass
+
+
+class TagTest(TestCase):
+ """Base testing setup for custom template tags"""
+
+ def setUp(self):
+ self.parser = StubParser(self.getContentText())
+
+ def getContentText(self):
+ return "content"
diff --git a/Pootle-2.0.0/external_apps/djblets/util/tests.py b/Pootle-2.0.0/external_apps/djblets/util/tests.py
new file mode 100644
index 0000000..5ecd0d1
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/djblets/util/tests.py
@@ -0,0 +1,243 @@
+#
+# tests.py -- Unit tests for classes in djblets.util
+#
+# Copyright (c) 2007-2008 Christian Hammond
+# Copyright (c) 2007-2008 David Trowbridge
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+import datetime
+import unittest
+
+from django.template import Token, TOKEN_TEXT, TemplateSyntaxError
+from django.utils.html import strip_spaces_between_tags
+
+from djblets.util.misc import cache_memoize
+from djblets.util.testing import TestCase, TagTest
+from djblets.util.templatetags import djblets_deco
+from djblets.util.templatetags import djblets_email
+from djblets.util.templatetags import djblets_utils
+
+
+def normalize_html(s):
+ return strip_spaces_between_tags(s).strip()
+
+
+class CacheTest(TestCase):
+ def testCacheMemoize(self):
+ """Testing cache_memoize"""
+ cacheKey = "abc123"
+ testStr = "Test 123"
+
+ def cacheFunc(cacheCalled=False):
+ self.assert_(not cacheCalled)
+ cacheCalled = True
+ return testStr
+
+ result = cache_memoize(cacheKey, cacheFunc)
+ self.assertEqual(result, testStr)
+
+ # Call a second time. We should only call cacheFunc once.
+ result = cache_memoize(cacheKey, cacheFunc)
+ self.assertEqual(result, testStr)
+
+
+class BoxTest(TagTest):
+ def testPlain(self):
+ """Testing box tag"""
+ node = djblets_deco.box(self.parser, Token(TOKEN_TEXT, 'box'))
+ context = {}
+
+ self.assertEqual(normalize_html(node.render(context)),
+ '<div class="box-container"><div class="box">' +
+ '<div class="box-inner">\ncontent\n ' +
+ '</div></div></div>')
+
+ def testClass(self):
+ """Testing box tag (with extra class)"""
+ node = djblets_deco.box(self.parser, Token(TOKEN_TEXT, 'box "class"'))
+ context = {}
+
+ self.assertEqual(normalize_html(node.render(context)),
+ '<div class="box-container"><div class="box class">' +
+ '<div class="box-inner">\ncontent\n ' +
+ '</div></div></div>')
+
+ def testError(self):
+ """Testing box tag (invalid usage)"""
+ self.assertRaises(TemplateSyntaxError,
+ lambda: djblets_deco.box(self.parser,
+ Token(TOKEN_TEXT,
+ 'box "class" "foo"')))
+
+
+class ErrorBoxTest(TagTest):
+ def testPlain(self):
+ """Testing errorbox tag"""
+ node = djblets_deco.errorbox(self.parser,
+ Token(TOKEN_TEXT, 'errorbox'))
+
+ context = {}
+
+ self.assertEqual(normalize_html(node.render(context)),
+ '<div class="errorbox">\ncontent\n</div>')
+
+ def testId(self):
+ """Testing errorbox tag (with id)"""
+ node = djblets_deco.errorbox(self.parser,
+ Token(TOKEN_TEXT, 'errorbox "id"'))
+
+ context = {}
+
+ self.assertEqual(normalize_html(node.render(context)),
+ '<div class="errorbox" id="id">\ncontent\n</div>')
+
+
+ def testError(self):
+ """Testing errorbox tag (invalid usage)"""
+ self.assertRaises(TemplateSyntaxError,
+ lambda: djblets_deco.errorbox(self.parser,
+ Token(TOKEN_TEXT,
+ 'errorbox "id" ' +
+ '"foo"')))
+
+
+class AgeIdTest(TagTest):
+ def setUp(self):
+ TagTest.setUp(self)
+
+ self.now = datetime.datetime.now()
+
+ self.context = {
+ 'now': self.now,
+ 'minus1': self.now - datetime.timedelta(1),
+ 'minus2': self.now - datetime.timedelta(2),
+ 'minus3': self.now - datetime.timedelta(3),
+ 'minus4': self.now - datetime.timedelta(4),
+ }
+
+ def testNow(self):
+ """Testing ageid tag (now)"""
+ self.assertEqual(djblets_utils.ageid(self.now), 'age1')
+
+ def testMinus1(self):
+ """Testing ageid tag (yesterday)"""
+ self.assertEqual(djblets_utils.ageid(self.now - datetime.timedelta(1)),
+ 'age2')
+
+ def testMinus2(self):
+ """Testing ageid tag (two days ago)"""
+ self.assertEqual(djblets_utils.ageid(self.now - datetime.timedelta(2)),
+ 'age3')
+
+ def testMinus3(self):
+ """Testing ageid tag (three days ago)"""
+ self.assertEqual(djblets_utils.ageid(self.now - datetime.timedelta(3)),
+ 'age4')
+
+ def testMinus4(self):
+ """Testing ageid tag (four days ago)"""
+ self.assertEqual(djblets_utils.ageid(self.now - datetime.timedelta(4)),
+ 'age5')
+
+ def testNotDateTime(self):
+ """Testing ageid tag (non-datetime object)"""
+ class Foo:
+ def __init__(self, now):
+ self.day = now.day
+ self.month = now.month
+ self.year = now.year
+
+ self.assertEqual(djblets_utils.ageid(Foo(self.now)), 'age1')
+
+
+class TestEscapeSpaces(unittest.TestCase):
+ def test(self):
+ """Testing escapespaces filter"""
+ self.assertEqual(djblets_utils.escapespaces('Hi there'),
+ 'Hi there')
+ self.assertEqual(djblets_utils.escapespaces('Hi there'),
+ 'Hi&nbsp; there')
+ self.assertEqual(djblets_utils.escapespaces('Hi there\n'),
+ 'Hi&nbsp; there<br />')
+
+
+class TestHumanizeList(unittest.TestCase):
+ def test0(self):
+ """Testing humanize_list filter (length 0)"""
+ self.assertEqual(djblets_utils.humanize_list([]), '')
+
+ def test1(self):
+ """Testing humanize_list filter (length 1)"""
+ self.assertEqual(djblets_utils.humanize_list(['a']), 'a')
+
+ def test2(self):
+ """Testing humanize_list filter (length 2)"""
+ self.assertEqual(djblets_utils.humanize_list(['a', 'b']), 'a and b')
+
+ def test3(self):
+ """Testing humanize_list filter (length 3)"""
+ self.assertEqual(djblets_utils.humanize_list(['a', 'b', 'c']),
+ 'a, b and c')
+
+ def test4(self):
+ """Testing humanize_list filter (length 4)"""
+ self.assertEqual(djblets_utils.humanize_list(['a', 'b', 'c', 'd']),
+ 'a, b, c, and d')
+
+
+class TestIndent(unittest.TestCase):
+ def test(self):
+ """Testing indent filter"""
+ self.assertEqual(djblets_utils.indent('foo'), ' foo')
+ self.assertEqual(djblets_utils.indent('foo', 3), ' foo')
+ self.assertEqual(djblets_utils.indent('foo\nbar'), ' foo\n bar')
+
+
+class QuotedEmailTagTest(TagTest):
+ def testInvalid(self):
+ """Testing quoted_email tag (invalid usage)"""
+ self.assertRaises(TemplateSyntaxError,
+ lambda: djblets_email.quoted_email(self.parser,
+ Token(TOKEN_TEXT, 'quoted_email')))
+
+
+class CondenseTagTest(TagTest):
+ def getContentText(self):
+ return "foo\nbar\n\n\n\n\n\n\nfoobar!"
+
+ def testPlain(self):
+ """Testing condense tag"""
+ node = djblets_email.condense(self.parser,
+ Token(TOKEN_TEXT, 'condense'))
+ self.assertEqual(node.render({}), "foo\nbar\n\n\nfoobar!")
+
+
+class QuoteTextFilterTest(unittest.TestCase):
+ def testPlain(self):
+ """Testing quote_text filter (default level)"""
+ self.assertEqual(djblets_email.quote_text("foo\nbar"),
+ "> foo\n> bar")
+
+ def testLevel2(self):
+ """Testing quote_text filter (level 2)"""
+ self.assertEqual(djblets_email.quote_text("foo\nbar", 2),
+ "> > foo\n> > bar")
diff --git a/Pootle-2.0.0/external_apps/profiles/__init__.py b/Pootle-2.0.0/external_apps/profiles/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/profiles/__init__.py
diff --git a/Pootle-2.0.0/external_apps/profiles/urls.py b/Pootle-2.0.0/external_apps/profiles/urls.py
new file mode 100644
index 0000000..bc922bf
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/profiles/urls.py
@@ -0,0 +1,43 @@
+"""
+URLConf for Django user profile management.
+
+Recommended usage is to use a call to ``include()`` in your project's
+root URLConf to include this URLConf for any URL beginning with
+'/profiles/'.
+
+If the default behavior of the profile views is acceptable to you,
+simply use a line like this in your root URLConf to set up the default
+URLs for profiles::
+
+ (r'^profiles/', include('profiles.urls')),
+
+But if you'd like to customize the behavior (e.g., by passing extra
+arguments to the various views) or split up the URLs, feel free to set
+up your own URL patterns for these views instead. If you do, it's a
+good idea to keep the name ``profiles_profile_detail`` for the pattern
+which points to the ``profile_detail`` view, since several views use
+``reverse()`` with that name to generate a default post-submission
+redirect. If you don't use that name, remember to explicitly pass
+``success_url`` to those views.
+
+"""
+
+from django.conf.urls.defaults import *
+
+from profiles import views
+
+
+urlpatterns = patterns('',
+ url(r'^create/$',
+ views.create_profile,
+ name='profiles_create_profile'),
+ url(r'^edit/$',
+ views.edit_profile,
+ name='profiles_edit_profile'),
+ url(r'^(?P<username>[^/]+)/$',
+ views.profile_detail,
+ name='profiles_profile_detail'),
+ url(r'^$',
+ views.profile_list,
+ name='profiles_profile_list'),
+ )
diff --git a/Pootle-2.0.0/external_apps/profiles/utils.py b/Pootle-2.0.0/external_apps/profiles/utils.py
new file mode 100644
index 0000000..faacfcb
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/profiles/utils.py
@@ -0,0 +1,45 @@
+"""
+Utility functions for retrieving and generating forms for the
+site-specific user profile model specified in the
+``AUTH_PROFILE_MODULE`` setting.
+
+"""
+
+from django import forms
+from django.conf import settings
+from django.contrib.auth.models import SiteProfileNotAvailable
+from django.db.models import get_model
+
+
+def get_profile_model():
+ """
+ Return the model class for the currently-active user profile
+ model, as defined by the ``AUTH_PROFILE_MODULE`` setting. If that
+ setting is missing, raise
+ ``django.contrib.auth.models.SiteProfileNotAvailable``.
+
+ """
+ if (not hasattr(settings, 'AUTH_PROFILE_MODULE')) or \
+ (not settings.AUTH_PROFILE_MODULE):
+ raise SiteProfileNotAvailable
+ profile_mod = get_model(*settings.AUTH_PROFILE_MODULE.split('.'))
+ if profile_mod is None:
+ raise SiteProfileNotAvailable
+ return profile_mod
+
+
+def get_profile_form():
+ """
+ Return a form class (a subclass of the default ``ModelForm``)
+ suitable for creating/editing instances of the site-specific user
+ profile model, as defined by the ``AUTH_PROFILE_MODULE``
+ setting. If that setting is missing, raise
+ ``django.contrib.auth.models.SiteProfileNotAvailable``.
+
+ """
+ profile_mod = get_profile_model()
+ class _ProfileForm(forms.ModelForm):
+ class Meta:
+ model = profile_mod
+ exclude = ('user',) # User will be filled in by the view.
+ return _ProfileForm
diff --git a/Pootle-2.0.0/external_apps/profiles/views.py b/Pootle-2.0.0/external_apps/profiles/views.py
new file mode 100644
index 0000000..7fb069b
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/profiles/views.py
@@ -0,0 +1,337 @@
+"""
+Views for creating, editing and viewing site-specific user profiles.
+
+"""
+
+from django.contrib.auth.decorators import login_required
+from django.contrib.auth.models import User
+from django.core.exceptions import ObjectDoesNotExist
+from django.core.urlresolvers import reverse
+from django.http import Http404
+from django.http import HttpResponseRedirect
+from django.shortcuts import get_object_or_404
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.views.generic.list_detail import object_list
+
+from profiles import utils
+
+
+def create_profile(request, form_class=None, success_url=None,
+ template_name='profiles/create_profile.html',
+ extra_context=None):
+ """
+ Create a profile for the current user, if one doesn't already
+ exist.
+
+ If the user already has a profile, as determined by
+ ``request.user.get_profile()``, a redirect will be issued to the
+ :view:`profiles.views.edit_profile` view. If no profile model has
+ been specified in the ``AUTH_PROFILE_MODULE`` setting,
+ ``django.contrib.auth.models.SiteProfileNotAvailable`` will be
+ raised.
+
+ **Optional arguments:**
+
+ ``extra_context``
+ A dictionary of variables to add to the template context. Any
+ callable object in this dictionary will be called to produce
+ the end result which appears in the context.
+
+ ``form_class``
+ The form class to use for validating and creating the user
+ profile. This form class must define a method named
+ ``save()``, implementing the same argument signature as the
+ ``save()`` method of a standard Django ``ModelForm`` (this
+ view will call ``save(commit=False)`` to obtain the profile
+ object, and fill in the user before the final save). If the
+ profile object includes many-to-many relations, the convention
+ established by ``ModelForm`` of using a method named
+ ``save_m2m()`` will be used, and so your form class should
+ also define this method.
+
+ If this argument is not supplied, this view will use a
+ ``ModelForm`` automatically generated from the model specified
+ by ``AUTH_PROFILE_MODULE``.
+
+ ``success_url``
+ The URL to redirect to after successful profile creation. If
+ this argument is not supplied, this will default to the URL of
+ :view:`profiles.views.profile_detail` for the newly-created
+ profile object.
+
+ ``template_name``
+ The template to use when displaying the profile-creation
+ form. If not supplied, this will default to
+ :template:`profiles/create_profile.html`.
+
+ **Context:**
+
+ ``form``
+ The profile-creation form.
+
+ **Template:**
+
+ ``template_name`` keyword argument, or
+ :template:`profiles/create_profile.html`.
+
+ """
+ try:
+ profile_obj = request.user.get_profile()
+ return HttpResponseRedirect(reverse('profiles_edit_profile'))
+ except ObjectDoesNotExist:
+ pass
+
+ #
+ # We set up success_url here, rather than as the default value for
+ # the argument. Trying to do it as the argument's default would
+ # mean evaluating the call to reverse() at the time this module is
+ # first imported, which introduces a circular dependency: to
+ # perform the reverse lookup we need access to profiles/urls.py,
+ # but profiles/urls.py in turn imports this module.
+ #
+
+ if success_url is None:
+ success_url = reverse('profiles_profile_detail',
+ kwargs={ 'username': request.user.username })
+ if form_class is None:
+ form_class = utils.get_profile_form()
+ if request.method == 'POST':
+ form = form_class(data=request.POST, files=request.FILES)
+ if form.is_valid():
+ profile_obj = form.save(commit=False)
+ profile_obj.user = request.user
+ profile_obj.save()
+ if hasattr(form, 'save_m2m'):
+ form.save_m2m()
+ return HttpResponseRedirect(success_url)
+ else:
+ form = form_class()
+
+ if extra_context is None:
+ extra_context = {}
+ context = RequestContext(request)
+ for key, value in extra_context.items():
+ context[key] = callable(value) and value() or value
+
+ return render_to_response(template_name,
+ { 'form': form },
+ context_instance=context)
+create_profile = login_required(create_profile)
+
+def edit_profile(request, form_class=None, success_url=None,
+ template_name='profiles/edit_profile.html',
+ extra_context=None):
+ """
+ Edit the current user's profile.
+
+ If the user does not already have a profile (as determined by
+ ``User.get_profile()``), a redirect will be issued to the
+ :view:`profiles.views.create_profile` view; if no profile model
+ has been specified in the ``AUTH_PROFILE_MODULE`` setting,
+ ``django.contrib.auth.models.SiteProfileNotAvailable`` will be
+ raised.
+
+ **Optional arguments:**
+
+ ``extra_context``
+ A dictionary of variables to add to the template context. Any
+ callable object in this dictionary will be called to produce
+ the end result which appears in the context.
+
+ ``form_class``
+ The form class to use for validating and editing the user
+ profile. This form class must operate similarly to a standard
+ Django ``ModelForm`` in that it must accept an instance of the
+ object to be edited as the keyword argument ``instance`` to
+ its constructor, and it must implement a method named
+ ``save()`` which will save the updates to the object. If this
+ argument is not specified, this view will use a ``ModelForm``
+ generated from the model specified in the
+ ``AUTH_PROFILE_MODULE`` setting.
+
+ ``success_url``
+ The URL to redirect to following a successful edit. If not
+ specified, this will default to the URL of
+ :view:`profiles.views.profile_detail` for the profile object
+ being edited.
+
+ ``template_name``
+ The template to use when displaying the profile-editing
+ form. If not specified, this will default to
+ :template:`profiles/edit_profile.html`.
+
+ **Context:**
+
+ ``form``
+ The form for editing the profile.
+
+ ``profile``
+ The user's current profile.
+
+ **Template:**
+
+ ``template_name`` keyword argument or
+ :template:`profiles/edit_profile.html`.
+
+ """
+ try:
+ profile_obj = request.user.get_profile()
+ except ObjectDoesNotExist:
+ return HttpResponseRedirect(reverse('profiles_create_profile'))
+
+ #
+ # See the comment in create_profile() for discussion of why
+ # success_url is set up here, rather than as a default value for
+ # the argument.
+ #
+
+ if success_url is None:
+ success_url = reverse('profiles_profile_detail',
+ kwargs={ 'username': request.user.username })
+ if form_class is None:
+ form_class = utils.get_profile_form()
+ if request.method == 'POST':
+ form = form_class(data=request.POST, files=request.FILES, instance=profile_obj)
+ if form.is_valid():
+ form.save()
+ return HttpResponseRedirect(success_url)
+ else:
+ form = form_class(instance=profile_obj)
+
+ if extra_context is None:
+ extra_context = {}
+ context = RequestContext(request)
+ for key, value in extra_context.items():
+ context[key] = callable(value) and value() or value
+
+ return render_to_response(template_name,
+ { 'form': form,
+ 'profile': profile_obj, },
+ context_instance=context)
+edit_profile = login_required(edit_profile)
+
+def profile_detail(request, username, public_profile_field=None,
+ template_name='profiles/profile_detail.html',
+ extra_context=None):
+ """
+ Detail view of a user's profile.
+
+ If no profile model has been specified in the
+ ``AUTH_PROFILE_MODULE`` setting,
+ ``django.contrib.auth.models.SiteProfileNotAvailable`` will be
+ raised.
+
+ If the user has not yet created a profile, ``Http404`` will be
+ raised.
+
+ **Required arguments:**
+
+ ``username``
+ The username of the user whose profile is being displayed.
+
+ **Optional arguments:**
+
+ ``extra_context``
+ A dictionary of variables to add to the template context. Any
+ callable object in this dictionary will be called to produce
+ the end result which appears in the context.
+
+ ``public_profile_field``
+ The name of a ``BooleanField`` on the profile model; if the
+ value of that field on the user's profile is ``False``, the
+ ``profile`` variable in the template will be ``None``. Use
+ this feature to allow users to mark their profiles as not
+ being publicly viewable.
+
+ If this argument is not specified, it will be assumed that all
+ users' profiles are publicly viewable.
+
+ ``template_name``
+ The name of the template to use for displaying the profile. If
+ not specified, this will default to
+ :template:`profiles/profile_detail.html`.
+
+ **Context:**
+
+ ``profile``
+ The user's profile, or ``None`` if the user's profile is not
+ publicly viewable (see the description of
+ ``public_profile_field`` above).
+
+ **Template:**
+
+ ``template_name`` keyword argument or
+ :template:`profiles/profile_detail.html`.
+
+ """
+ user = get_object_or_404(User, username=username)
+ try:
+ profile_obj = user.get_profile()
+ except ObjectDoesNotExist:
+ raise Http404
+ if public_profile_field is not None and \
+ not getattr(profile_obj, public_profile_field):
+ profile_obj = None
+
+ if extra_context is None:
+ extra_context = {}
+ context = RequestContext(request)
+ for key, value in extra_context.items():
+ context[key] = callable(value) and value() or value
+
+ return render_to_response(template_name,
+ { 'profile': profile_obj },
+ context_instance=context)
+
+def profile_list(request, public_profile_field=None,
+ template_name='profiles/profile_list.html', **kwargs):
+ """
+ A list of user profiles.
+
+ If no profile model has been specified in the
+ ``AUTH_PROFILE_MODULE`` setting,
+ ``django.contrib.auth.models.SiteProfileNotAvailable`` will be
+ raised.
+
+ **Optional arguments:**
+
+ ``public_profile_field``
+ The name of a ``BooleanField`` on the profile model; if the
+ value of that field on a user's profile is ``False``, that
+ profile will be excluded from the list. Use this feature to
+ allow users to mark their profiles as not being publicly
+ viewable.
+
+ If this argument is not specified, it will be assumed that all
+ users' profiles are publicly viewable.
+
+ ``template_name``
+ The name of the template to use for displaying the profiles. If
+ not specified, this will default to
+ :template:`profiles/profile_list.html`.
+
+ Additionally, all arguments accepted by the
+ :view:`django.views.generic.list_detail.object_list` generic view
+ will be accepted here, and applied in the same fashion, with one
+ exception: ``queryset`` will always be the ``QuerySet`` of the
+ model specified by the ``AUTH_PROFILE_MODULE`` setting, optionally
+ filtered to remove non-publicly-viewable proiles.
+
+ **Context:**
+
+ Same as the :view:`django.views.generic.list_detail.object_list`
+ generic view.
+
+ **Template:**
+
+ ``template_name`` keyword argument or
+ :template:`profiles/profile_list.html`.
+
+ """
+ profile_model = utils.get_profile_model()
+ queryset = profile_model._default_manager.all()
+ if public_profile_field is not None:
+ queryset = queryset.filter(**{ public_profile_field: True })
+ kwargs['queryset'] = queryset
+ return object_list(request, template_name=template_name, **kwargs)
diff --git a/Pootle-2.0.0/external_apps/registration/__init__.py b/Pootle-2.0.0/external_apps/registration/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/registration/__init__.py
diff --git a/Pootle-2.0.0/external_apps/registration/admin.py b/Pootle-2.0.0/external_apps/registration/admin.py
new file mode 100644
index 0000000..3f36c18
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/registration/admin.py
@@ -0,0 +1,11 @@
+from django.contrib import admin
+
+from registration.models import RegistrationProfile
+
+
+class RegistrationAdmin(admin.ModelAdmin):
+ list_display = ('__unicode__', 'activation_key_expired')
+ search_fields = ('user__username', 'user__first_name')
+
+
+admin.site.register(RegistrationProfile, RegistrationAdmin)
diff --git a/Pootle-2.0.0/external_apps/registration/forms.py b/Pootle-2.0.0/external_apps/registration/forms.py
new file mode 100644
index 0000000..93505ad
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/registration/forms.py
@@ -0,0 +1,134 @@
+"""
+Forms and validation code for user registration.
+
+"""
+
+
+from django.contrib.auth.models import User
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+
+from registration.models import RegistrationProfile
+
+
+# I put this on all required fields, because it's easier to pick up
+# on them with CSS or JavaScript if they have a class of "required"
+# in the HTML. Your mileage may vary. If/when Django ticket #3515
+# lands in trunk, this will no longer be necessary.
+attrs_dict = { 'class': 'required' }
+
+
+class RegistrationForm(forms.Form):
+ """
+ Form for registering a new user account.
+
+ Validates that the requested username is not already in use, and
+ requires the password to be entered twice to catch typos.
+
+ Subclasses should feel free to add any additional validation they
+ need, but should either preserve the base ``save()`` or implement
+ a ``save()`` method which returns a ``User``.
+
+ """
+ username = forms.RegexField(regex=r'^\S+$',
+ max_length=30,
+ widget=forms.TextInput(attrs=attrs_dict),
+ label=_(u'username'))
+ email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
+ maxlength=75)),
+ label=_(u'email address'))
+ password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
+ label=_(u'password'))
+ password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
+ label=_(u'password (again)'))
+
+ def clean_username(self):
+ """
+ Validate that the username is alphanumeric and is not already
+ in use.
+
+ """
+ try:
+ user = User.objects.get(username__iexact=self.cleaned_data['username'])
+ except User.DoesNotExist:
+ return self.cleaned_data['username']
+ raise forms.ValidationError(_(u'This username is already taken. Please choose another.'))
+
+ def clean(self):
+ """
+ Verifiy that the values entered into the two password fields
+ match. Note that an error here will end up in
+ ``non_field_errors()`` because it doesn't apply to a single
+ field.
+
+ """
+ if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
+ if self.cleaned_data['password1'] != self.cleaned_data['password2']:
+ raise forms.ValidationError(_(u'You must type the same password each time'))
+ return self.cleaned_data
+
+ def save(self):
+ """
+ Create the new ``User`` and ``RegistrationProfile``, and
+ returns the ``User`` (by calling
+ ``RegistrationProfile.objects.create_inactive_user()``).
+
+ """
+ new_user = RegistrationProfile.objects.create_inactive_user(username=self.cleaned_data['username'],
+ password=self.cleaned_data['password1'],
+ email=self.cleaned_data['email'])
+ return new_user
+
+
+class RegistrationFormTermsOfService(RegistrationForm):
+ """
+ Subclass of ``RegistrationForm`` which adds a required checkbox
+ for agreeing to a site's Terms of Service.
+
+ """
+ tos = forms.BooleanField(widget=forms.CheckboxInput(attrs=attrs_dict),
+ label=_(u'I have read and agree to the Terms of Service'),
+ error_messages={ 'required': u"You must agree to the terms to register" })
+
+
+class RegistrationFormUniqueEmail(RegistrationForm):
+ """
+ Subclass of ``RegistrationForm`` which enforces uniqueness of
+ email addresses.
+
+ """
+ def clean_email(self):
+ """
+ Validate that the supplied email address is unique for the
+ site.
+
+ """
+ if User.objects.filter(email__iexact=self.cleaned_data['email']):
+ raise forms.ValidationError(_(u'This email address is already in use. Please supply a different email address.'))
+ return self.cleaned_data['email']
+
+
+class RegistrationFormNoFreeEmail(RegistrationForm):
+ """
+ Subclass of ``RegistrationForm`` which disallows registration with
+ email addresses from popular free webmail services; moderately
+ useful for preventing automated spam registrations.
+
+ To change the list of banned domains, subclass this form and
+ override the attribute ``bad_domains``.
+
+ """
+ bad_domains = ['aim.com', 'aol.com', 'email.com', 'gmail.com',
+ 'googlemail.com', 'hotmail.com', 'hushmail.com',
+ 'msn.com', 'mail.ru', 'mailinator.com', 'live.com']
+
+ def clean_email(self):
+ """
+ Check the supplied email address against a list of known free
+ webmail domains.
+
+ """
+ email_domain = self.cleaned_data['email'].split('@')[1]
+ if email_domain in self.bad_domains:
+ raise forms.ValidationError(_(u'Registration using free email addresses is prohibited. Please supply a different email address.'))
+ return self.cleaned_data['email']
diff --git a/Pootle-2.0.0/external_apps/registration/models.py b/Pootle-2.0.0/external_apps/registration/models.py
new file mode 100644
index 0000000..c3df9e1
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/registration/models.py
@@ -0,0 +1,255 @@
+import datetime
+import random
+import re
+
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.contrib.sites.models import Site
+from django.db import models
+from django.db import transaction
+from django.template.loader import render_to_string
+from django.utils.hashcompat import sha_constructor
+from django.utils.translation import ugettext_lazy as _
+
+
+SHA1_RE = re.compile('^[a-f0-9]{40}$')
+
+
+class RegistrationManager(models.Manager):
+ """
+ Custom manager for the ``RegistrationProfile`` model.
+
+ The methods defined here provide shortcuts for account creation
+ and activation (including generation and emailing of activation
+ keys), and for cleaning out expired inactive accounts.
+
+ """
+ def activate_user(self, activation_key):
+ """
+ Validate an activation key and activate the corresponding
+ ``User`` if valid.
+
+ If the key is valid and has not expired, return the ``User``
+ after activating.
+
+ If the key is not valid or has expired, return ``False``.
+
+ If the key is valid but the ``User`` is already active,
+ return ``False``.
+
+ To prevent reactivation of an account which has been
+ deactivated by site administrators, the activation key is
+ reset to the string constant ``RegistrationProfile.ACTIVATED``
+ after successful activation.
+
+ To execute customized logic when a ``User`` is activated,
+ connect a function to the signal
+ ``registration.signals.user_activated``; this signal will be
+ sent (with the ``User`` as the value of the keyword argument
+ ``user``) after a successful activation.
+
+ """
+ from registration.signals import user_activated
+
+ # Make sure the key we're trying conforms to the pattern of a
+ # SHA1 hash; if it doesn't, no point trying to look it up in
+ # the database.
+ if SHA1_RE.search(activation_key):
+ try:
+ profile = self.get(activation_key=activation_key)
+ except self.model.DoesNotExist:
+ return False
+ if not profile.activation_key_expired():
+ user = profile.user
+ user.is_active = True
+ user.save()
+ profile.activation_key = self.model.ACTIVATED
+ profile.save()
+ user_activated.send(sender=self.model, user=user)
+ return user
+ return False
+
+ def create_inactive_user(self, username, password, email,
+ send_email=True):
+ """
+ Create a new, inactive ``User``, generate a
+ ``RegistrationProfile`` and email its activation key to the
+ ``User``, returning the new ``User``.
+
+ To disable the email, call with ``send_email=False``.
+
+ The activation email will make use of two templates:
+
+ ``registration/activation_email_subject.txt``
+ This template will be used for the subject line of the
+ email. It receives one context variable, ``site``, which
+ is the currently-active
+ ``django.contrib.sites.models.Site`` instance. Because it
+ is used as the subject line of an email, this template's
+ output **must** be only a single line of text; output
+ longer than one line will be forcibly joined into only a
+ single line.
+
+ ``registration/activation_email.txt``
+ This template will be used for the body of the email. It
+ will receive three context variables: ``activation_key``
+ will be the user's activation key (for use in constructing
+ a URL to activate the account), ``expiration_days`` will
+ be the number of days for which the key will be valid and
+ ``site`` will be the currently-active
+ ``django.contrib.sites.models.Site`` instance.
+
+ To execute customized logic once the new ``User`` has been
+ created, connect a function to the signal
+ ``registration.signals.user_registered``; this signal will be
+ sent (with the new ``User`` as the value of the keyword
+ argument ``user``) after the ``User`` and
+ ``RegistrationProfile`` have been created, and the email (if
+ any) has been sent..
+
+ """
+ from registration.signals import user_registered
+
+ new_user = User.objects.create_user(username, email, password)
+ new_user.is_active = False
+ new_user.save()
+
+ registration_profile = self.create_profile(new_user)
+
+ if send_email:
+ from django.core.mail import send_mail
+ current_site = Site.objects.get_current()
+
+ subject = render_to_string('registration/activation_email_subject.txt',
+ { 'site': current_site })
+ # Email subject *must not* contain newlines
+ subject = ''.join(subject.splitlines())
+
+ message = render_to_string('registration/activation_email.txt',
+ { 'activation_key': registration_profile.activation_key,
+ 'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS,
+ 'site': current_site })
+
+ send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [new_user.email])
+ user_registered.send(sender=self.model, user=new_user)
+ return new_user
+ create_inactive_user = transaction.commit_on_success(create_inactive_user)
+
+ def create_profile(self, user):
+ """
+ Create a ``RegistrationProfile`` for a given
+ ``User``, and return the ``RegistrationProfile``.
+
+ The activation key for the ``RegistrationProfile`` will be a
+ SHA1 hash, generated from a combination of the ``User``'s
+ username and a random salt.
+
+ """
+ salt = sha_constructor(str(random.random())).hexdigest()[:5]
+ activation_key = sha_constructor(salt+user.email).hexdigest()
+ return self.create(user=user,
+ activation_key=activation_key)
+
+ def delete_expired_users(self):
+ """
+ Remove expired instances of ``RegistrationProfile`` and their
+ associated ``User``s.
+
+ Accounts to be deleted are identified by searching for
+ instances of ``RegistrationProfile`` with expired activation
+ keys, and then checking to see if their associated ``User``
+ instances have the field ``is_active`` set to ``False``; any
+ ``User`` who is both inactive and has an expired activation
+ key will be deleted.
+
+ It is recommended that this method be executed regularly as
+ part of your routine site maintenance; this application
+ provides a custom management command which will call this
+ method, accessible as ``manage.py cleanupregistration``.
+
+ Regularly clearing out accounts which have never been
+ activated serves two useful purposes:
+
+ 1. It alleviates the ocasional need to reset a
+ ``RegistrationProfile`` and/or re-send an activation email
+ when a user does not receive or does not act upon the
+ initial activation email; since the account will be
+ deleted, the user will be able to simply re-register and
+ receive a new activation key.
+
+ 2. It prevents the possibility of a malicious user registering
+ one or more accounts and never activating them (thus
+ denying the use of those usernames to anyone else); since
+ those accounts will be deleted, the usernames will become
+ available for use again.
+
+ If you have a troublesome ``User`` and wish to disable their
+ account while keeping it in the database, simply delete the
+ associated ``RegistrationProfile``; an inactive ``User`` which
+ does not have an associated ``RegistrationProfile`` will not
+ be deleted.
+
+ """
+ for profile in self.all():
+ if profile.activation_key_expired():
+ user = profile.user
+ if not user.is_active:
+ user.delete()
+
+
+class RegistrationProfile(models.Model):
+ """
+ A simple profile which stores an activation key for use during
+ user account registration.
+
+ Generally, you will not want to interact directly with instances
+ of this model; the provided manager includes methods
+ for creating and activating new accounts, as well as for cleaning
+ out accounts which have never been activated.
+
+ While it is possible to use this model as the value of the
+ ``AUTH_PROFILE_MODULE`` setting, it's not recommended that you do
+ so. This model's sole purpose is to store data temporarily during
+ account registration and activation.
+
+ """
+ ACTIVATED = u"ALREADY_ACTIVATED"
+
+ user = models.ForeignKey(User, unique=True, verbose_name=_('user'))
+ activation_key = models.CharField(_('activation key'), max_length=40)
+
+ objects = RegistrationManager()
+
+ class Meta:
+ verbose_name = _('registration profile')
+ verbose_name_plural = _('registration profiles')
+
+ def __unicode__(self):
+ return u"Registration information for %s" % self.user
+
+ def activation_key_expired(self):
+ """
+ Determine whether this ``RegistrationProfile``'s activation
+ key has expired, returning a boolean -- ``True`` if the key
+ has expired.
+
+ Key expiration is determined by a two-step process:
+
+ 1. If the user has already activated, the key will have been
+ reset to the string constant ``ACTIVATED``. Re-activating
+ is not permitted, and so this method returns ``True`` in
+ this case.
+
+ 2. Otherwise, the date the user signed up is incremented by
+ the number of days specified in the setting
+ ``ACCOUNT_ACTIVATION_DAYS`` (which should be the number of
+ days after signup during which a user is allowed to
+ activate their account); if the result is less than or
+ equal to the current date, the key has expired and this
+ method returns ``True``.
+
+ """
+ expiration_date = datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS)
+ return self.activation_key == self.ACTIVATED or \
+ (self.user.date_joined + expiration_date <= datetime.datetime.now())
+ activation_key_expired.boolean = True
diff --git a/Pootle-2.0.0/external_apps/registration/signals.py b/Pootle-2.0.0/external_apps/registration/signals.py
new file mode 100644
index 0000000..2a6eed9
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/registration/signals.py
@@ -0,0 +1,8 @@
+from django.dispatch import Signal
+
+
+# A new user has registered.
+user_registered = Signal(providing_args=["user"])
+
+# A user has activated his or her account.
+user_activated = Signal(providing_args=["user"])
diff --git a/Pootle-2.0.0/external_apps/registration/tests.py b/Pootle-2.0.0/external_apps/registration/tests.py
new file mode 100644
index 0000000..0f26553
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/registration/tests.py
@@ -0,0 +1,355 @@
+"""
+Unit tests for django-registration.
+
+These tests assume that you've completed all the prerequisites for
+getting django-registration running in the default setup, to wit:
+
+1. You have ``registration`` in your ``INSTALLED_APPS`` setting.
+
+2. You have created all of the templates mentioned in this
+ application's documentation.
+
+3. You have added the setting ``ACCOUNT_ACTIVATION_DAYS`` to your
+ settings file.
+
+4. You have URL patterns pointing to the registration and activation
+ views, with the names ``registration_register`` and
+ ``registration_activate``, respectively, and a URL pattern named
+ 'registration_complete'.
+
+"""
+
+import datetime
+import sha
+
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.core import mail
+from django.core import management
+from django.core.urlresolvers import reverse
+from django.test import TestCase
+
+from registration import forms
+from registration.models import RegistrationProfile
+from registration import signals
+
+
+class RegistrationTestCase(TestCase):
+ """
+ Base class for the test cases; this sets up two users -- one
+ expired, one not -- which are used to exercise various parts of
+ the application.
+
+ """
+ def setUp(self):
+ self.sample_user = RegistrationProfile.objects.create_inactive_user(username='alice',
+ password='secret',
+ email='alice@example.com')
+ self.expired_user = RegistrationProfile.objects.create_inactive_user(username='bob',
+ password='swordfish',
+ email='bob@example.com')
+ self.expired_user.date_joined -= datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS + 1)
+ self.expired_user.save()
+
+
+class RegistrationModelTests(RegistrationTestCase):
+ """
+ Tests for the model-oriented functionality of django-registration,
+ including ``RegistrationProfile`` and its custom manager.
+
+ """
+ def test_new_user_is_inactive(self):
+ """
+ Test that a newly-created user is inactive.
+
+ """
+ self.failIf(self.sample_user.is_active)
+
+ def test_registration_profile_created(self):
+ """
+ Test that a ``RegistrationProfile`` is created for a new user.
+
+ """
+ self.assertEqual(RegistrationProfile.objects.count(), 2)
+
+ def test_activation_email(self):
+ """
+ Test that user signup sends an activation email.
+
+ """
+ self.assertEqual(len(mail.outbox), 2)
+
+ def test_activation_email_disable(self):
+ """
+ Test that activation email can be disabled.
+
+ """
+ RegistrationProfile.objects.create_inactive_user(username='noemail',
+ password='foo',
+ email='nobody@example.com',
+ send_email=False)
+ self.assertEqual(len(mail.outbox), 2)
+
+ def test_activation(self):
+ """
+ Test that user activation actually activates the user and
+ properly resets the activation key, and fails for an
+ already-active or expired user, or an invalid key.
+
+ """
+ # Activating a valid user returns the user.
+ self.failUnlessEqual(RegistrationProfile.objects.activate_user(RegistrationProfile.objects.get(user=self.sample_user).activation_key).pk,
+ self.sample_user.pk)
+
+ # The activated user must now be active.
+ self.failUnless(User.objects.get(pk=self.sample_user.pk).is_active)
+
+ # The activation key must now be reset to the "already activated" constant.
+ self.failUnlessEqual(RegistrationProfile.objects.get(user=self.sample_user).activation_key,
+ RegistrationProfile.ACTIVATED)
+
+ # Activating an expired user returns False.
+ self.failIf(RegistrationProfile.objects.activate_user(RegistrationProfile.objects.get(user=self.expired_user).activation_key))
+
+ # Activating from a key that isn't a SHA1 hash returns False.
+ self.failIf(RegistrationProfile.objects.activate_user('foo'))
+
+ # Activating from a key that doesn't exist returns False.
+ self.failIf(RegistrationProfile.objects.activate_user(sha.new('foo').hexdigest()))
+
+ def test_account_expiration_condition(self):
+ """
+ Test that ``RegistrationProfile.activation_key_expired()``
+ returns ``True`` for expired users and for active users, and
+ ``False`` otherwise.
+
+ """
+ # Unexpired user returns False.
+ self.failIf(RegistrationProfile.objects.get(user=self.sample_user).activation_key_expired())
+
+ # Expired user returns True.
+ self.failUnless(RegistrationProfile.objects.get(user=self.expired_user).activation_key_expired())
+
+ # Activated user returns True.
+ RegistrationProfile.objects.activate_user(RegistrationProfile.objects.get(user=self.sample_user).activation_key)
+ self.failUnless(RegistrationProfile.objects.get(user=self.sample_user).activation_key_expired())
+
+ def test_expired_user_deletion(self):
+ """
+ Test that
+ ``RegistrationProfile.objects.delete_expired_users()`` deletes
+ only inactive users whose activation window has expired.
+
+ """
+ RegistrationProfile.objects.delete_expired_users()
+ self.assertEqual(RegistrationProfile.objects.count(), 1)
+
+ def test_management_command(self):
+ """
+ Test that ``manage.py cleanupregistration`` functions
+ correctly.
+
+ """
+ management.call_command('cleanupregistration')
+ self.assertEqual(RegistrationProfile.objects.count(), 1)
+
+ def test_signals(self):
+ """
+ Test that the ``user_registered`` and ``user_activated``
+ signals are sent, and that they send the ``User`` as an
+ argument.
+
+ """
+ def receiver(sender, **kwargs):
+ self.assert_('user' in kwargs)
+ self.assertEqual(kwargs['user'].username, u'signal_test')
+ received_signals.append(kwargs.get('signal'))
+
+ received_signals = []
+ expected_signals = [signals.user_registered, signals.user_activated]
+ for signal in expected_signals:
+ signal.connect(receiver)
+
+ RegistrationProfile.objects.create_inactive_user(username='signal_test',
+ password='foo',
+ email='nobody@example.com',
+ send_email=False)
+ RegistrationProfile.objects.activate_user(RegistrationProfile.objects.get(user__username='signal_test').activation_key)
+
+ self.assertEqual(received_signals, expected_signals)
+
+
+class RegistrationFormTests(RegistrationTestCase):
+ """
+ Tests for the forms and custom validation logic included in
+ django-registration.
+
+ """
+ def test_registration_form(self):
+ """
+ Test that ``RegistrationForm`` enforces username constraints
+ and matching passwords.
+
+ """
+ invalid_data_dicts = [
+ # Non-alphanumeric username.
+ {
+ 'data':
+ { 'username': 'foo/bar',
+ 'email': 'foo@example.com',
+ 'password1': 'foo',
+ 'password2': 'foo' },
+ 'error':
+ ('username', [u"Enter a valid value."])
+ },
+ # Already-existing username.
+ {
+ 'data':
+ { 'username': 'alice',
+ 'email': 'alice@example.com',
+ 'password1': 'secret',
+ 'password2': 'secret' },
+ 'error':
+ ('username', [u"This username is already taken. Please choose another."])
+ },
+ # Mismatched passwords.
+ {
+ 'data':
+ { 'username': 'foo',
+ 'email': 'foo@example.com',
+ 'password1': 'foo',
+ 'password2': 'bar' },
+ 'error':
+ ('__all__', [u"You must type the same password each time"])
+ },
+ ]
+
+ for invalid_dict in invalid_data_dicts:
+ form = forms.RegistrationForm(data=invalid_dict['data'])
+ self.failIf(form.is_valid())
+ self.assertEqual(form.errors[invalid_dict['error'][0]], invalid_dict['error'][1])
+
+ form = forms.RegistrationForm(data={ 'username': 'foo',
+ 'email': 'foo@example.com',
+ 'password1': 'foo',
+ 'password2': 'foo' })
+ self.failUnless(form.is_valid())
+
+ def test_registration_form_tos(self):
+ """
+ Test that ``RegistrationFormTermsOfService`` requires
+ agreement to the terms of service.
+
+ """
+ form = forms.RegistrationFormTermsOfService(data={ 'username': 'foo',
+ 'email': 'foo@example.com',
+ 'password1': 'foo',
+ 'password2': 'foo' })
+ self.failIf(form.is_valid())
+ self.assertEqual(form.errors['tos'], [u"You must agree to the terms to register"])
+
+ form = forms.RegistrationFormTermsOfService(data={ 'username': 'foo',
+ 'email': 'foo@example.com',
+ 'password1': 'foo',
+ 'password2': 'foo',
+ 'tos': 'on' })
+ self.failUnless(form.is_valid())
+
+ def test_registration_form_unique_email(self):
+ """
+ Test that ``RegistrationFormUniqueEmail`` validates uniqueness
+ of email addresses.
+
+ """
+ form = forms.RegistrationFormUniqueEmail(data={ 'username': 'foo',
+ 'email': 'alice@example.com',
+ 'password1': 'foo',
+ 'password2': 'foo' })
+ self.failIf(form.is_valid())
+ self.assertEqual(form.errors['email'], [u"This email address is already in use. Please supply a different email address."])
+
+ form = forms.RegistrationFormUniqueEmail(data={ 'username': 'foo',
+ 'email': 'foo@example.com',
+ 'password1': 'foo',
+ 'password2': 'foo' })
+ self.failUnless(form.is_valid())
+
+ def test_registration_form_no_free_email(self):
+ """
+ Test that ``RegistrationFormNoFreeEmail`` disallows
+ registration with free email addresses.
+
+ """
+ base_data = { 'username': 'foo',
+ 'password1': 'foo',
+ 'password2': 'foo' }
+ for domain in ('aim.com', 'aol.com', 'email.com', 'gmail.com',
+ 'googlemail.com', 'hotmail.com', 'hushmail.com',
+ 'msn.com', 'mail.ru', 'mailinator.com', 'live.com'):
+ invalid_data = base_data.copy()
+ invalid_data['email'] = u"foo@%s" % domain
+ form = forms.RegistrationFormNoFreeEmail(data=invalid_data)
+ self.failIf(form.is_valid())
+ self.assertEqual(form.errors['email'], [u"Registration using free email addresses is prohibited. Please supply a different email address."])
+
+ base_data['email'] = 'foo@example.com'
+ form = forms.RegistrationFormNoFreeEmail(data=base_data)
+ self.failUnless(form.is_valid())
+
+
+class RegistrationViewTests(RegistrationTestCase):
+ """
+ Tests for the views included in django-registration.
+
+ """
+ def test_registration_view(self):
+ """
+ Test that the registration view rejects invalid submissions,
+ and creates a new user and redirects after a valid submission.
+
+ """
+ # Invalid data fails.
+ response = self.client.post(reverse('registration_register'),
+ data={ 'username': 'alice', # Will fail on username uniqueness.
+ 'email': 'foo@example.com',
+ 'password1': 'foo',
+ 'password2': 'foo' })
+ self.assertEqual(response.status_code, 200)
+ self.failUnless(response.context['form'])
+ self.failUnless(response.context['form'].errors)
+
+ response = self.client.post(reverse('registration_register'),
+ data={ 'username': 'foo',
+ 'email': 'foo@example.com',
+ 'password1': 'foo',
+ 'password2': 'foo' })
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(response['Location'], 'http://testserver%s' % reverse('registration_complete'))
+ self.assertEqual(RegistrationProfile.objects.count(), 3)
+
+ def test_activation_view(self):
+ """
+ Test that the activation view activates the user from a valid
+ key and fails if the key is invalid or has expired.
+
+ """
+ # Valid user puts the user account into the context.
+ response = self.client.get(reverse('registration_activate',
+ kwargs={ 'activation_key': RegistrationProfile.objects.get(user=self.sample_user).activation_key }))
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['account'].pk, self.sample_user.pk)
+
+ # Expired user sets the account to False.
+ response = self.client.get(reverse('registration_activate',
+ kwargs={ 'activation_key': RegistrationProfile.objects.get(user=self.expired_user).activation_key }))
+ self.failIf(response.context['account'])
+
+ # Invalid key gets to the view, but sets account to False.
+ response = self.client.get(reverse('registration_activate',
+ kwargs={ 'activation_key': 'foo' }))
+ self.failIf(response.context['account'])
+
+ # Nonexistent key sets the account to False.
+ response = self.client.get(reverse('registration_activate',
+ kwargs={ 'activation_key': sha.new('foo').hexdigest() }))
+ self.failIf(response.context['account'])
diff --git a/Pootle-2.0.0/external_apps/registration/urls.py b/Pootle-2.0.0/external_apps/registration/urls.py
new file mode 100644
index 0000000..2a4cacd
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/registration/urls.py
@@ -0,0 +1,72 @@
+"""
+URLConf for Django user registration and authentication.
+
+If the default behavior of the registration views is acceptable to
+you, simply use a line like this in your root URLConf to set up the
+default URLs for registration::
+
+ (r'^accounts/', include('registration.urls')),
+
+This will also automatically set up the views in
+``django.contrib.auth`` at sensible default locations.
+
+But if you'd like to customize the behavior (e.g., by passing extra
+arguments to the various views) or split up the URLs, feel free to set
+up your own URL patterns for these views instead. If you do, it's a
+good idea to use the names ``registration_activate``,
+``registration_complete`` and ``registration_register`` for the
+various steps of the user-signup process.
+
+"""
+
+
+from django.conf.urls.defaults import *
+from django.views.generic.simple import direct_to_template
+from django.contrib.auth import views as auth_views
+
+from registration.views import activate
+from registration.views import register
+
+
+urlpatterns = patterns('',
+ # Activation keys get matched by \w+ instead of the more specific
+ # [a-fA-F0-9]{40} because a bad activation key should still get to the view;
+ # that way it can return a sensible "invalid key" message instead of a
+ # confusing 404.
+ url(r'^activate/(?P<activation_key>\w+)/$',
+ activate,
+ name='registration_activate'),
+ url(r'^login/$',
+ auth_views.login,
+ {'template_name': 'registration/login.html'},
+ name='auth_login'),
+ url(r'^logout/$',
+ auth_views.logout,
+ {'template_name': 'registration/logout.html'},
+ name='auth_logout'),
+ url(r'^password/change/$',
+ auth_views.password_change,
+ name='auth_password_change'),
+ url(r'^password/change/done/$',
+ auth_views.password_change_done,
+ name='auth_password_change_done'),
+ url(r'^password/reset/$',
+ auth_views.password_reset,
+ name='auth_password_reset'),
+ url(r'^password/reset/confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',
+ auth_views.password_reset_confirm,
+ name='auth_password_reset_confirm'),
+ url(r'^password/reset/complete/$',
+ auth_views.password_reset_complete,
+ name='auth_password_reset_complete'),
+ url(r'^password/reset/done/$',
+ auth_views.password_reset_done,
+ name='auth_password_reset_done'),
+ url(r'^register/$',
+ register,
+ name='registration_register'),
+ url(r'^register/complete/$',
+ direct_to_template,
+ {'template': 'registration/registration_complete.html'},
+ name='registration_complete'),
+ )
diff --git a/Pootle-2.0.0/external_apps/registration/views.py b/Pootle-2.0.0/external_apps/registration/views.py
new file mode 100644
index 0000000..5238a26
--- /dev/null
+++ b/Pootle-2.0.0/external_apps/registration/views.py
@@ -0,0 +1,153 @@
+"""
+Views which allow users to create and activate accounts.
+
+"""
+
+
+from django.conf import settings
+from django.core.urlresolvers import reverse
+from django.http import HttpResponseRedirect
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+
+from registration.forms import RegistrationForm
+from registration.models import RegistrationProfile
+
+
+def activate(request, activation_key,
+ template_name='registration/activate.html',
+ extra_context=None):
+ """
+ Activate a ``User``'s account from an activation key, if their key
+ is valid and hasn't expired.
+
+ By default, use the template ``registration/activate.html``; to
+ change this, pass the name of a template as the keyword argument
+ ``template_name``.
+
+ **Required arguments**
+
+ ``activation_key``
+ The activation key to validate and use for activating the
+ ``User``.
+
+ **Optional arguments**
+
+ ``extra_context``
+ A dictionary of variables to add to the template context. Any
+ callable object in this dictionary will be called to produce
+ the end result which appears in the context.
+
+ ``template_name``
+ A custom template to use.
+
+ **Context:**
+
+ ``account``
+ The ``User`` object corresponding to the account, if the
+ activation was successful. ``False`` if the activation was not
+ successful.
+
+ ``expiration_days``
+ The number of days for which activation keys stay valid after
+ registration.
+
+ Any extra variables supplied in the ``extra_context`` argument
+ (see above).
+
+ **Template:**
+
+ registration/activate.html or ``template_name`` keyword argument.
+
+ """
+ activation_key = activation_key.lower() # Normalize before trying anything with it.
+ account = RegistrationProfile.objects.activate_user(activation_key)
+ if extra_context is None:
+ extra_context = {}
+ context = RequestContext(request)
+ for key, value in extra_context.items():
+ context[key] = callable(value) and value() or value
+ return render_to_response(template_name,
+ { 'account': account,
+ 'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS },
+ context_instance=context)
+
+
+def register(request, success_url=None,
+ form_class=RegistrationForm,
+ template_name='registration/registration_form.html',
+ extra_context=None):
+ """
+ Allow a new user to register an account.
+
+ Following successful registration, issue a redirect; by default,
+ this will be whatever URL corresponds to the named URL pattern
+ ``registration_complete``, which will be
+ ``/accounts/register/complete/`` if using the included URLConf. To
+ change this, point that named pattern at another URL, or pass your
+ preferred URL as the keyword argument ``success_url``.
+
+ By default, ``registration.forms.RegistrationForm`` will be used
+ as the registration form; to change this, pass a different form
+ class as the ``form_class`` keyword argument. The form class you
+ specify must have a method ``save`` which will create and return
+ the new ``User``.
+
+ By default, use the template
+ ``registration/registration_form.html``; to change this, pass the
+ name of a template as the keyword argument ``template_name``.
+
+ **Required arguments**
+
+ None.
+
+ **Optional arguments**
+
+ ``form_class``
+ The form class to use for registration.
+
+ ``extra_context``
+ A dictionary of variables to add to the template context. Any
+ callable object in this dictionary will be called to produce
+ the end result which appears in the context.
+
+ ``success_url``
+ The URL to redirect to on successful registration.
+
+ ``template_name``
+ A custom template to use.
+
+ **Context:**
+
+ ``form``
+ The registration form.
+
+ Any extra variables supplied in the ``extra_context`` argument
+ (see above).
+
+ **Template:**
+
+ registration/registration_form.html or ``template_name`` keyword
+ argument.
+
+ """
+ if request.method == 'POST':
+ form = form_class(data=request.POST, files=request.FILES)
+ if form.is_valid():
+ new_user = form.save()
+ # success_url needs to be dynamically generated here; setting a
+ # a default value using reverse() will cause circular-import
+ # problems with the default URLConf for this application, which
+ # imports this file.
+ return HttpResponseRedirect(success_url or reverse('registration_complete'))
+ else:
+ form = form_class()
+
+ if extra_context is None:
+ extra_context = {}
+ context = RequestContext(request)
+ for key, value in extra_context.items():
+ context[key] = callable(value) and value() or value
+ return render_to_response(template_name,
+ { 'form': form },
+ context_instance=context)