Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Adding baph to public github

  • Loading branch information...
commit d5a8178c0d53c711d106499eefdd57f5e46f17b1 0 parents
@dlrust dlrust authored
Showing with 8,639 additions and 0 deletions.
  1. +5 −0 .gitignore
  2. +23 −0 README.rst
  3. 0  baph/__init__.py
  4. +61 −0 baph/auth/__init__.py
  5. +40 −0 baph/auth/backends.py
  6. +76 −0 baph/auth/forms.py
  7. +32 −0 baph/auth/middleware.py
  8. +263 −0 baph/auth/models.py
  9. 0  baph/auth/registration/__init__.py
  10. 0  baph/auth/registration/backends/__init__.py
  11. +126 −0 baph/auth/registration/backends/default/__init__.py
  12. +55 −0 baph/auth/registration/backends/default/urls.py
  13. +91 −0 baph/auth/registration/forms.py
  14. 0  baph/auth/registration/management/__init__.py
  15. 0  baph/auth/registration/management/commands/__init__.py
  16. +18 −0 baph/auth/registration/management/commands/cleanupregistration.py
  17. +267 −0 baph/auth/registration/models.py
  18. +9 −0 baph/auth/registration/signals.py
  19. +17 −0 baph/auth/registration/views.py
  20. +11 −0 baph/auth/urls.py
  21. +166 −0 baph/auth/views.py
  22. +18 −0 baph/context_processors/__init__.py
  23. 0  baph/db/__init__.py
  24. 0  baph/db/management/__init__.py
  25. 0  baph/db/management/commands/__init__.py
  26. +21 −0 baph/db/management/commands/syncsqlalchemy.py
  27. +122 −0 baph/db/models.py
  28. +174 −0 baph/db/orm.py
  29. +16 −0 baph/db/shortcuts.py
  30. +62 −0 baph/db/types.py
  31. 0  baph/decorators/__init__.py
  32. +30 −0 baph/decorators/db.py
  33. +134 −0 baph/decorators/json.py
  34. 0  baph/localflavor/__init__.py
  35. +284 −0 baph/localflavor/generic/__init__.py
  36. +138 −0 baph/localflavor/generic/forms.py
  37. 0  baph/localflavor/generic/models.py
  38. +59 −0 baph/localflavor/generic/static/localflavor/generic/js/stateprovince.js
  39. 0  baph/middleware/__init__.py
  40. +42 −0 baph/middleware/debug.py
  41. +32 −0 baph/middleware/orm.py
  42. +62 −0 baph/middleware/ssl.py
  43. 0  baph/piston/__init__.py
  44. +176 −0 baph/piston/models.py
  45. 0  baph/piston/oauth/__init__.py
  46. 0  baph/piston/oauth/store/__init__.py
  47. +115 −0 baph/piston/oauth/store/sqlalchemy.py
  48. +10 −0 baph/piston/oauth/urls.py
  49. +66 −0 baph/piston/oauth/views.py
  50. 0  baph/sessions/__init__.py
  51. 0  baph/sessions/backends/__init__.py
  52. +95 −0 baph/sessions/backends/memcache.py
  53. +87 −0 baph/sessions/backends/sqlalchemy.py
  54. +23 −0 baph/sessions/models.py
  55. 0  baph/sites/__init__.py
  56. +89 −0 baph/sites/models.py
  57. 0  baph/socialmedia/__init__.py
  58. +172 −0 baph/socialmedia/facebook/__init__.py
  59. +95 −0 baph/socialmedia/facebook/connect/__init__.py
  60. +22 −0 baph/socialmedia/facebook/connect/auth.py
  61. +21 −0 baph/socialmedia/facebook/connect/models.py
  62. +17 −0 baph/socialmedia/facebook/connect/templates/fb_connect/javascript.html
  63. +2 −0  baph/socialmedia/facebook/connect/templates/fb_connect/login-button.html
  64. +13 −0 baph/socialmedia/facebook/connect/templates/fb_connect/register.html
  65. +25 −0 baph/socialmedia/facebook/connect/urls.py
  66. +33 −0 baph/socialmedia/facebook/connect/views.py
  67. +130 −0 baph/socialmedia/twitter/__init__.py
  68. +116 −0 baph/socialmedia/twitter/auth/__init__.py
  69. +14 −0 baph/socialmedia/twitter/auth/forms.py
  70. +37 −0 baph/socialmedia/twitter/auth/models.py
  71. +4 −0 baph/socialmedia/twitter/auth/templates/twitter/auth/complete.html
  72. +1 −0  baph/socialmedia/twitter/auth/templates/twitter/auth/register.html
  73. +11 −0 baph/socialmedia/twitter/auth/urls.py
  74. +73 −0 baph/socialmedia/twitter/auth/views.py
  75. 0  baph/template/__init__.py
  76. 0  baph/template/ext/__init__.py
  77. +46 −0 baph/template/shortcuts.py
  78. 0  baph/test/__init__.py
  79. +101 −0 baph/test/base.py
  80. +13 −0 baph/test/client.py
  81. +79 −0 baph/test/oauth.py
  82. +257 −0 baph/test/requestfactory.py
  83. 0  baph/utils/__init__.py
  84. +104 −0 baph/utils/assets/__init__.py
  85. +79 −0 baph/utils/assets/css.py
  86. +118 −0 baph/utils/assets/jinja2.py
  87. +17 −0 baph/utils/assets/js.py
  88. 0  baph/utils/assets/management/__init__.py
  89. 0  baph/utils/assets/management/commands/__init__.py
  90. +131 −0 baph/utils/assets/management/commands/deploy_static.py
  91. +27 −0 baph/utils/collections.py
  92. +138 −0 baph/utils/importing.py
  93. +24 −0 baph/utils/path.py
  94. +215 −0 baph/utils/s3.py
  95. +241 −0 docs/_ext/djangodocs.py
  96. +37 −0 docs/auth.rst
  97. +325 −0 docs/conf.py
  98. +2 −0  docs/context_processors.rst
  99. +2 −0  docs/db-models.rst
  100. +2 −0  docs/db-types.rst
  101. +10 −0 docs/decorators.rst
  102. +37 −0 docs/index.rst
  103. +9 −0 docs/localflavor.rst
  104. +15 −0 docs/middleware.rst
  105. +2 −0  docs/orm.rst
  106. +11 −0 docs/sessions.rst
  107. +6 −0 docs/sites.rst
  108. +9 −0 docs/socialmedia.rst
  109. +6 −0 docs/template.rst
  110. +18 −0 docs/utils.rst
  111. +25 −0 requirements.txt
  112. +15 −0 runtests.py
  113. +17 −0 setup.py
  114. 0  tests/__init__.py
  115. 0  tests/auth/__init__.py
  116. 0  tests/auth/registration/__init__.py
  117. +155 −0 tests/auth/registration/test_forms.py
  118. +256 −0 tests/auth/registration/test_models.py
  119. +282 −0 tests/auth/registration/test_views.py
  120. +81 −0 tests/auth/registration/urls.py
  121. +129 −0 tests/auth/test_models.py
  122. +52 −0 tests/auth/test_profile.py
  123. +203 −0 tests/auth/test_views.py
  124. 0  tests/decorators/__init__.py
  125. +28 −0 tests/decorators/test_db.py
  126. +97 −0 tests/decorators/test_json.py
  127. +17 −0 tests/decorators/urls.py
  128. +66 −0 tests/decorators/views.py
  129. 0  tests/localflavor/__init__.py
  130. +76 −0 tests/localflavor/test_generic.py
  131. 0  tests/piston/__init__.py
  132. +82 −0 tests/piston/test_models.py
  133. +105 −0 tests/piston/test_oauth.py
  134. 0  tests/project/__init__.py
  135. 0  tests/project/api/__init__.py
  136. +11 −0 tests/project/api/handlers.py
  137. +18 −0 tests/project/api/urls.py
  138. +17 −0 tests/project/manage.py
  139. +124 −0 tests/project/settings.py
  140. 0  tests/project/templates/piston/oauth/authorize_verification_code.html
  141. 0  tests/project/templates/piston/oauth/challenge.html
  142. 0  tests/project/templates/registration/activate.html
  143. 0  tests/project/templates/registration/activation_complete.html
  144. 0  tests/project/templates/registration/activation_email.txt
  145. 0  tests/project/templates/registration/activation_email_subject.txt
  146. 0  tests/project/templates/registration/logged_out.html
  147. +1 −0  tests/project/templates/registration/login.html
  148. +1 −0  tests/project/templates/registration/password_reset_complete.html
  149. +5 −0 tests/project/templates/registration/password_reset_confirm.html
  150. +1 −0  tests/project/templates/registration/password_reset_done.html
  151. +1 −0  tests/project/templates/registration/password_reset_email.html
  152. +1 −0  tests/project/templates/registration/password_reset_form.html
  153. 0  tests/project/templates/registration/registration_closed.html
  154. 0  tests/project/templates/registration/registration_complete.html
  155. 0  tests/project/templates/registration/registration_form.html
  156. 0  tests/project/templates/registration/test_template_name.html
  157. +1 −0  tests/project/templates/test_alt_renderer.html
  158. +23 −0 tests/project/urls.py
  159. +5 −0 tests/ssl_urls.py
  160. +89 −0 tests/test_db_types.py
  161. +170 −0 tests/test_models.py
  162. +239 −0 tests/test_orm.py
  163. +77 −0 tests/test_sites.py
  164. +28 −0 tests/test_ssl.py
  165. 0  tests/utils/__init__.py
  166. +29 −0 tests/utils/test_collections.py
  167. +101 −0 tests/utils/test_importing.py
  168. +29 −0 tests/utils/test_path.py
5 .gitignore
@@ -0,0 +1,5 @@
+*.egg-info
+*.pyc
+.*.sw?
+build
+docs/build
23 README.rst
@@ -0,0 +1,23 @@
+====
+Baph
+====
+
+Baph (short for "`Baphomet`_") is a derivative of the `Django`_ framework that
+uses `SQLAlchemy`_ and `Jinja2`_ instead of Django's built-in database ORM and
+templating solutions, respectively.
+
+Installation
+------------
+
+Run ``python setup.py install``.
+
+Documentation
+-------------
+
+Run ``python setup.py build_sphinx`` and read the generated documentation in
+``build/sphinx/html``. You may want to start at ``index.html``.
+
+.. _Baphomet: http://en.wikipedia.org/wiki/Baphomet
+.. _Django: http://www.djangoproject.com/
+.. _SQLAlchemy: http://www.sqlalchemy.org/
+.. _Jinja2: http://jinja.pocoo.org/2/
0  baph/__init__.py
No changes.
61 baph/auth/__init__.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+'''SQLAlchemy versions of :mod:`django.contrib.auth` utility functions.'''
+
+from datetime import datetime
+from django.contrib.auth import BACKEND_SESSION_KEY, load_backend, SESSION_KEY
+
+
+def login(request, user):
+ '''Persist a user id and a backend in the request. This way a user doesn't
+ have to reauthenticate on every request.
+
+ :param user: The user object.
+ :type user: :class:`baph.auth.models.User`
+ '''
+ if hasattr(request, 'orm'):
+ session = request.orm.sessionmaker()
+ else:
+ from .models import orm
+ session = orm.sessionmaker()
+ if user is None:
+ user = request.user
+ # TODO: It would be nice to support different login methods, like signed
+ # cookies.
+ user.last_login = datetime.now()
+ session.commit()
+
+ if SESSION_KEY in request.session:
+ if request.session[SESSION_KEY] != user.id:
+ # To avoid reusing another user's session, create a new, empty
+ # session if the existing session corresponds to a different
+ # authenticated user.
+ request.session.flush()
+ else:
+ request.session.cycle_key()
+ request.session[SESSION_KEY] = user.id
+ request.session[BACKEND_SESSION_KEY] = user.backend
+ if hasattr(request, 'user'):
+ request.user = user
+
+
+def logout(request):
+ '''Removes the authenticated user's ID from the request and flushes their
+ session data.
+ '''
+ request.session.flush()
+ if hasattr(request, 'user'):
+ from .models import AnonymousUser
+ request.user = AnonymousUser()
+
+
+def get_user(request):
+ '''Retrieves the object representing the current user.'''
+ from .models import AnonymousUser
+ try:
+ user_id = request.session[SESSION_KEY]
+ backend_path = request.session[BACKEND_SESSION_KEY]
+ backend = load_backend(backend_path)
+ user = backend.get_user(user_id) or AnonymousUser()
+ except KeyError:
+ user = AnonymousUser()
+ return user
40 baph/auth/backends.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+'''\
+=========================================================================
+:mod:`baph.auth.backends` -- SQLAlchemy backend for Django Authentication
+=========================================================================
+
+.. moduleauthor:: Mark Lee <markl@evomediagroup.com>
+'''
+
+from .models import orm, User
+
+
+class SQLAlchemyBackend(object):
+ '''Authentication backend using SQLAlchemy. See
+ :setting:`AUTHENTICATION_BACKENDS` for details on
+ setting this class as the authentication backend for your project.
+ '''
+
+ supports_object_permissions = False
+ supports_anonymous_user = True
+
+ def authenticate(self, username=None, password=None, session=None):
+ # TODO: Model, login attribute name and password attribute name
+ # should be configurable.
+ if not session:
+ session = orm.sessionmaker()
+ user = session.query(User) \
+ .filter_by(username=username) \
+ .first()
+ if user is None:
+ return user
+ elif user.check_password(password):
+ return user
+ else:
+ return None
+
+ def get_user(self, user_id, session=None):
+ if not session:
+ session = orm.sessionmaker()
+ return session.query(User).get(user_id)
76 baph/auth/forms.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+from baph.sites.models import get_current_site
+from coffin.shortcuts import render_to_string
+from django import forms
+from django.contrib.auth.forms import SetPasswordForm as BaseSetPasswordForm
+from django.contrib.auth.tokens import default_token_generator
+from django.utils.http import int_to_base36
+from django.utils.translation import ugettext_lazy as _
+from .models import orm, UNUSABLE_PASSWORD, User
+
+
+class PasswordResetForm(forms.Form):
+ email = forms.EmailField(label=_('E-mail'), max_length=75)
+
+ def clean_email(self):
+ '''Validates that a user exists with the given e-mail address.'''
+ email = self.cleaned_data['email']
+ session = orm.sessionmaker()
+ users = session.query(User) \
+ .filter_by(email=email)
+ if users.count() == 0:
+ raise forms.ValidationError(_(u'''\
+That e-mail address doesn't have an associated user account. Are you sure
+you've registered?'''))
+ self.users_cache = users.filter(User.password != UNUSABLE_PASSWORD) \
+ .all()
+ if len(self.users_cache) == 0:
+ raise forms.ValidationError(_(u'''\
+That e-mail address doesn't allow the password to be set.'''))
+ return email
+
+ def save(self, domain_override=None,
+ email_template_name='registration/password_reset_email.html',
+ use_https=False, token_generator=default_token_generator,
+ request=None):
+ '''Generates a one-use only link for resetting password and sends to
+ the user.
+ '''
+ from django.core.mail import send_mail
+ for user in self.users_cache:
+ if not domain_override:
+ current_site = get_current_site(request)
+ site_name = current_site.name
+ domain = current_site.domain
+ else:
+ site_name = domain = domain_override
+ c = {
+ 'email': user.email,
+ 'domain': domain,
+ 'site_name': site_name,
+ 'uid': int_to_base36(user.id.int),
+ 'user': user,
+ 'token': token_generator.make_token(user),
+ 'protocol': use_https and 'https' or 'http',
+ }
+ body = render_to_string(email_template_name, c)
+ send_mail(_("Password reset on %s") % site_name,
+ body, None, [user.email])
+
+
+class SetPasswordForm(BaseSetPasswordForm):
+ '''A form that lets a user change set his/her password without entering
+ the old password.
+ '''
+
+ def __init__(self, user, *args, **kwargs):
+ self.user = user
+ self.session = kwargs.pop('session', orm.sessionmaker())
+ super(SetPasswordForm, self).__init__(user, *args, **kwargs)
+
+ def save(self, commit=True):
+ self.user.set_password(self.cleaned_data['new_password1'])
+ if commit:
+ self.session.commit()
+ return self.user
32 baph/auth/middleware.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+'''\
+==========================================================================
+:mod:`baph.auth.middleware` -- Django+SQLAlchemy Authentication Middleware
+==========================================================================
+
+.. moduleauthor:: Mark Lee <markl@evomediagroup.com>
+'''
+
+
+class LazyUser(object):
+ '''Allows for the lazy retrieval of the :class:`baph.auth.models.User`
+ object.
+ '''
+ def __get__(self, request, obj_type=None):
+ if not hasattr(request, '_cached_user'):
+ from . import get_user
+ request._cached_user = get_user(request)
+ return request._cached_user
+
+
+class AuthenticationMiddleware(object):
+ '''See :class:`django.contrib.auth.middleware.AuthenticationMiddleware`.
+ '''
+
+ def process_request(self, request):
+ assert hasattr(request, 'session'), '''\
+The Django authentication middleware requires session middleware to be
+installed. Edit your MIDDLEWARE_CLASSES setting to insert
+"django.contrib.sessions.middleware.SessionMiddleware".'''
+ request.__class__.user = LazyUser()
+ return None
263 baph/auth/models.py
@@ -0,0 +1,263 @@
+# -*- coding: utf-8 -*-
+'''\
+======================================================================
+:mod:`baph.auth.models` -- SQLAlchemy models for Django Authentication
+======================================================================
+
+.. moduleauthor:: Mark Lee <markl@evomediagroup.com>
+
+SQLAlchemy versions of models from :mod:`django.contrib.auth.models`.
+
+.. setting:: AUTH_DIGEST_ALGORITHM
+
+Setting: AUTH_DIGEST_ALGORITHM
+------------------------------
+
+The default hash algorithm used to set passwords with. Defaults to ``sha1``.
+'''
+
+import inspect
+
+from baph.db.models import Model
+from baph.db.orm import ORM
+from baph.db.types import UUID
+from baph.utils.importing import import_attr
+from datetime import datetime
+from django.conf import settings
+(AnonymousUser, check_password, base_get_hexdigest, SiteProfileNotAvailable,
+ UNUSABLE_PASSWORD) = \
+ import_attr(['django.contrib.auth.models'],
+ ['AnonymousUser', 'check_password', 'get_hexdigest',
+ 'SiteProfileNotAvailable', 'UNUSABLE_PASSWORD'])
+from django.core.exceptions import ImproperlyConfigured
+from django.utils.encoding import smart_str
+from django.utils.importlib import import_module
+from django.utils.translation import ugettext as _
+import hashlib
+import random
+from sqlalchemy import Boolean, Column, DateTime, Integer, String, Unicode
+from sqlalchemy.orm import synonym
+import urllib
+import uuid
+
+orm = ORM.get()
+
+AUTH_USER_FIELD_TYPE = getattr(settings, 'AUTH_USER_FIELD_TYPE', 'UUID')
+AUTH_USER_FIELD = UUID if AUTH_USER_FIELD_TYPE == 'UUID' else Integer(10)
+
+
+def get_hexdigest(algorithm, salt, raw_password):
+ '''Extends Django's :func:`django.contrib.auth.models.get_hexdigest` by
+ adding support for all of the other available hash algorithms.
+
+ Inspired by http://u.malept.com/djpwdhash
+ '''
+ if (hasattr(hashlib, 'algorithms') and algorithm in hashlib.algorithms):
+ return hashlib.new(algorithm, salt + raw_password).hexdigest()
+ elif algorithm in ('sha224', 'sha256', 'sha384', 'sha512'):
+ return getattr(hashlib, algorithm)(salt + raw_password).hexdigest()
+ else:
+ return base_get_hexdigest(algorithm, salt, raw_password)
+
+# fun with monkeypatching
+exec inspect.getsource(check_password)
+
+def _generate_user_id_column():
+ if AUTH_USER_FIELD_TYPE != 'UUID':
+ return Column(AUTH_USER_FIELD, primary_key=True)
+ return Column(UUID, primary_key=True, default=uuid.uuid4)
+
+class User(orm.Base, Model):
+ '''The SQLAlchemy model for Django's ``auth_user`` table.
+ Users within the Django authentication system are represented by this
+ model.
+
+ Username and password are required. Other fields are optional.
+ '''
+ __tablename__ = 'auth_user'
+
+ id = _generate_user_id_column()
+ '''Unique ID of the :class:`User`.'''
+ username = Column(Unicode(75), nullable=False, unique=True)
+ '''See :attr:`django.contrib.auth.models.User.username`.'''
+ first_name = Column(Unicode(30), nullable=True)
+ '''See :attr:`django.contrib.auth.models.User.first_name`.'''
+ last_name = Column(Unicode(30), nullable=True)
+ '''See :attr:`django.contrib.auth.models.User.last_name`.'''
+ _email = Column('email', String(settings.EMAIL_FIELD_LENGTH), index=True,
+ nullable=False)
+ '''See :attr:`django.contrib.auth.models.User.email`.'''
+ password = Column(String(256), nullable=False)
+ '''See :attr:`django.contrib.auth.models.User.password`.'''
+ is_staff = Column(Boolean, default=False, nullable=False)
+ '''See :attr:`django.contrib.auth.models.User.is_staff`.'''
+ is_active = Column(Boolean, default=True, nullable=False)
+ '''See :attr:`django.contrib.auth.models.User.is_active`.'''
+ is_superuser = Column(Boolean, default=False, nullable=False)
+ '''See :attr:`django.contrib.auth.models.User.is_superuser`.'''
+ last_login = Column(DateTime, default=datetime.now, nullable=False,
+ onupdate=datetime.now)
+ '''See :attr:`django.contrib.auth.models.User.last_login`.'''
+ date_joined = Column(DateTime, default=datetime.now, nullable=False)
+ '''See :attr:`django.contrib.auth.models.User.date_joined`.'''
+
+ def _get_email(self):
+ '''See :attr:`django.contrib.auth.models.User.email`.'''
+ return self._email
+
+ def _set_email(self, value):
+ # Normalize the address by lowercasing the domain part of the email
+ # address.
+ try:
+ email_name, domain_part = value.strip().split('@', 1)
+ except ValueError:
+ raise ValueError(_('The email address supplied is invalid.'))
+ else:
+ self._email = '@'.join([email_name, domain_part.lower()])
+
+ email = synonym('_email', descriptor=property(_get_email, _set_email))
+
+ def get_absolute_url(self):
+ '''The absolute path to a user's profile.
+
+ :rtype: :class:`str`
+ '''
+ return '/users/%s/' % urllib.quote(smart_str(self.username))
+
+ def check_password(self, raw_password):
+ '''Tests if the password given matches the password of the user.'''
+ if self.password == UNUSABLE_PASSWORD:
+ return False
+ return check_password(raw_password, self.password)
+
+ def email_user(self, subject, message, from_email=None, **kwargs):
+ '''Sends an e-mail to this User.'''
+ from django.core.mail import send_mail
+ if not from_email:
+ from_email = getattr(settings, 'DEFAULT_FROM_EMAIL', None)
+ send_mail(subject, message, from_email, [self.email], **kwargs)
+
+ @staticmethod
+ def generate_salt(algo='sha1'):
+ '''Generates a salt for generating digests.'''
+ return get_hexdigest(algo, str(random.random()),
+ str(random.random()))[:5]
+
+ def get_full_name(self):
+ '''Retrieves the first_name plus the last_name, with a space in
+ between and no leading/trailing whitespace.
+ '''
+ full_name = u'%s %s' % (self.first_name, self.last_name)
+ return full_name.strip()
+
+ def get_profile(self, session=None):
+ '''Returns site-specific profile for this user. Raises
+ :exc:`~django.contrib.auth.models.SiteProfileNotAvailable` if this
+ site does not allow profiles.
+ '''
+ if not hasattr(self, '_profile_cache'):
+ from django.conf import settings
+ if not getattr(settings, 'AUTH_PROFILE_MODULE', False):
+ raise SiteProfileNotAvailable('''\
+You need to set AUTH_PROFILE_MODULE in your project settings''')
+ try:
+ app_label, model_name = settings.AUTH_PROFILE_MODULE \
+ .rsplit('.', 1)
+ except ValueError:
+ raise SiteProfileNotAvailable('''\
+app_label and model_name should be separated by a dot in the
+AUTH_PROFILE_MODULE setting''')
+
+ try:
+ module = import_module(app_label)
+ model_cls = getattr(module, model_name, None)
+ if model_cls is None:
+ raise SiteProfileNotAvailable('''\
+Unable to load the profile model, check AUTH_PROFILE_MODULE in your project
+settings''')
+ if not session:
+ session = orm.sessionmaker()
+ self._profile_cache = session.query(model_cls) \
+ .get(self.id)
+ except (ImportError, ImproperlyConfigured):
+ raise SiteProfileNotAvailable
+ return self._profile_cache
+
+ def has_usable_password(self):
+ '''Determines whether the user has a password.'''
+ return self.password != UNUSABLE_PASSWORD
+
+ def is_anonymous(self):
+ '''Always returns :const:`False`. This is a way of comparing
+ :class:`User` objects to anonymous users.
+ '''
+ return False
+
+ def is_authenticated(self):
+ '''Tells if a user's authenticated. Always :const:`True` for this
+ class.
+ '''
+ return True
+
+ def set_password(self, raw_password, algo=None):
+ '''Copy of :meth:`django.contrib.auth.models.User.set_password`.
+
+ The important difference: it takes an optional ``algo`` parameter,
+ which can change the hash method used to one-way encrypt the password.
+ The fallback used is the :setting:`AUTH_DIGEST_ALGORITHM` setting,
+ followed by the default in Django, ``sha1``.
+ '''
+ if not algo:
+ algo = getattr(settings, 'AUTH_DIGEST_ALGORITHM', 'sha1')
+ salt = self.generate_salt(algo)
+ hsh = get_hexdigest(algo, salt, raw_password)
+ self.password = '%s$%s$%s' % (algo, salt, hsh)
+
+ def set_unusable_password(self):
+ '''Sets a password value that will never be a valid hash.'''
+ self.password = UNUSABLE_PASSWORD
+
+ def __repr__(self):
+ return '<User(%s, "%s")>' % (self.id, self.get_full_name())
+
+ def __unicode__(self):
+ return unicode(self.username)
+
+ @classmethod
+ def create_user(cls, username, email, password=None, session=None):
+ '''Creates a new User.'''
+ if not session:
+ session = orm.sessionmaker()
+ user = cls(username=username, email=email, is_staff=False,
+ is_active=True, is_superuser=False)
+ if password:
+ user.set_password(password)
+ else:
+ user.set_unusable_password()
+ session.add(user)
+ session.commit()
+ return user
+
+ @classmethod
+ def create_staff(cls, username, email, password, session=None):
+ '''Creates a new User with staff privileges.'''
+ if not session:
+ session = orm.sessionmaker()
+ user = cls(username=username, email=email, is_staff=True,
+ is_active=True, is_superuser=False)
+ user.set_password(password)
+ session.add(user)
+ session.commit()
+ return user
+
+ @classmethod
+ def create_superuser(cls, username, email, password, session=None):
+ '''Creates a new User with superuser privileges.'''
+ if not session:
+ session = orm.sessionmaker()
+ user = cls(username=username, email=email, is_staff=True,
+ is_active=True, is_superuser=True)
+ user.set_password(password)
+ session.add(user)
+ session.commit()
+ return user
0  baph/auth/registration/__init__.py
No changes.
0  baph/auth/registration/backends/__init__.py
No changes.
126 baph/auth/registration/backends/default/__init__.py
@@ -0,0 +1,126 @@
+# -*- coding: utf-8 -*-
+
+from baph.auth.registration import signals
+from baph.auth.registration.forms import RegistrationForm
+from baph.auth.registration.models import RegistrationProfile
+from baph.sites.models import get_current_site
+from django.conf import settings
+
+
+class DefaultBackend(object):
+ """
+ A registration backend which follows a simple workflow:
+
+ 1. User signs up, inactive account is created.
+
+ 2. Email is sent to user with activation link.
+
+ 3. User clicks activation link, account is now active.
+
+ Using this backend requires that
+
+ * ``registration`` be listed in the ``INSTALLED_APPS`` setting
+ (since this backend makes use of models defined in this
+ application).
+
+ * The setting ``ACCOUNT_ACTIVATION_DAYS`` be supplied, specifying
+ (as an integer) the number of days from registration during
+ which a user may activate their account (after that period
+ expires, activation will be disallowed).
+
+ * The creation of the templates
+ ``registration/activation_email_subject.txt`` and
+ ``registration/activation_email.txt``, which will be used for
+ the activation email. See the notes for this backends
+ ``register`` method for details regarding these templates.
+
+ Additionally, registration can be temporarily closed by adding the
+ setting ``REGISTRATION_OPEN`` and setting it to
+ ``False``. Omitting this setting, or setting it to ``True``, will
+ be interpreted as meaning that registration is currently open and
+ permitted.
+
+ Internally, this is accomplished via storing an activation key in
+ an instance of ``registration.models.RegistrationProfile``. See
+ that model and its custom manager for full documentation of its
+ fields and supported operations.
+
+ """
+ def register(self, request, **kwargs):
+ '''Given a username, email address and password, register a new
+ user account, which will initially be inactive.
+
+ Along with the new ``User`` object, a new
+ ``registration.models.RegistrationProfile`` will be created,
+ tied to that ``User``, containing the activation key which
+ will be used for this account.
+
+ An email will be sent to the supplied email address; this
+ email should contain an activation link. The email will be
+ rendered using two templates. See the documentation for
+ ``RegistrationProfile.send_activation_email()`` for
+ information about these templates and the contexts provided to
+ them.
+
+ After the ``User`` and ``RegistrationProfile`` are created and
+ the activation email is sent, the signal
+ ``registration.signals.user_registered`` will be sent, with
+ the new ``User`` as the keyword argument ``user`` and the
+ class of this backend as the sender.
+ '''
+ username, email, password = (kwargs['username'], kwargs['email'],
+ kwargs['password1'])
+ site = get_current_site(request)
+
+ new_user = RegistrationProfile.create_inactive_user(username, email,
+ password, site)
+ signals.user_registered.send(sender=self.__class__,
+ user=new_user,
+ request=request)
+ return new_user
+
+ def activate(self, request, activation_key):
+ '''Given an an activation key, look up and activate the user
+ account corresponding to that key (if possible).
+
+ After successful activation, the signal
+ ``registration.signals.user_activated`` will be sent, with the
+ newly activated ``User`` as the keyword argument ``user`` and
+ the class of this backend as the sender.
+ '''
+ activated = RegistrationProfile.activate_user(activation_key)
+ if activated:
+ signals.user_activated.send(sender=self.__class__,
+ user=activated,
+ request=request)
+ return activated
+
+ def registration_allowed(self, request):
+ '''Indicate whether account registration is currently permitted,
+ based on the value of the setting ``REGISTRATION_OPEN``. This
+ is determined as follows:
+
+ * If ``REGISTRATION_OPEN`` is not specified in settings, or is
+ set to ``True``, registration is permitted.
+
+ * If ``REGISTRATION_OPEN`` is both specified and set to
+ ``False``, registration is not permitted.
+
+ '''
+ return getattr(settings, 'REGISTRATION_OPEN', True)
+
+ def get_form_class(self, request):
+ '''Return the default form class used for user registration.'''
+ return RegistrationForm
+
+ def post_registration_redirect(self, request, user):
+ '''Return the name of the URL to redirect to after successful user
+ registration.
+ '''
+ return ('registration_complete', (), {})
+
+ def post_activation_redirect(self, request, user):
+ '''Return the name of the URL to redirect to after successful account
+ activation.
+ '''
+ return ('registration_activation_complete', (), {})
55 baph/auth/registration/backends/default/urls.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+'''URLconf for registration and activation, using django-registration's
+default backend.
+
+If the default behavior of these 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.backends.default.urls')),
+
+This will also automatically set up the views in
+``django.contrib.auth`` at sensible default locations.
+
+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.
+'''
+
+from baph.auth.registration.views import activate, register
+from coffin.conf.urls.defaults import include, patterns, url
+from coffin.views.generic.simple import direct_to_template
+
+BACKEND = 'baph.auth.registration.backends.default.DefaultBackend'
+
+
+urlpatterns = patterns('',
+ url(r'^activate/complete/$',
+ direct_to_template,
+ {'template': 'registration/activation_complete.html'},
+ name='registration_activation_complete'),
+ # 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,
+ {'backend': BACKEND},
+ name='registration_activate'),
+ url(r'^register/$',
+ register,
+ {
+ 'backend': BACKEND,
+ 'SSL': True,
+ },
+ name='registration_register'),
+ url(r'^register/complete/$',
+ direct_to_template,
+ {'template': 'registration/registration_complete.html'},
+ name='registration_complete'),
+ url(r'^register/closed/$',
+ direct_to_template,
+ {'template': 'registration/registration_closed.html'},
+ name='registration_disallowed'),
+ (r'', include('baph.auth.urls')),
+)
91 baph/auth/registration/forms.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+'''
+:mod:`baph.auth.registration.forms` -- Registration-related Forms
+=================================================================
+
+Forms and validation code for user registration.
+'''
+
+from baph.auth.models import User
+from baph.db.orm import ORM
+from baph.utils.importing import import_any_module
+dforms = import_any_module(['django.forms'])
+from django.utils.translation import ugettext_lazy as _
+forms = import_any_module(['registration.forms'])
+
+Checkbox = dforms.CheckboxInput
+orm = ORM.get()
+TERMS_ERROR_MSG = _(u'You must agree to the terms to register')
+TERMS_LABEL = _(u'I have read and agree to the Terms of Service')
+
+
+class RegistrationForm(forms.RegistrationForm):
+ '''An SQLAlchemy-based version of django-registration's
+ ``RegistrationForm``.
+ '''
+
+ def clean_username(self):
+ '''Validate that the username is alphanumeric and is not already in
+ use.
+ '''
+ session = orm.sessionmaker()
+ user_ct = session.query(User) \
+ .filter_by(username=self.cleaned_data['username']) \
+ .count()
+ if user_ct == 0:
+ return self.cleaned_data['username']
+ else:
+ raise dforms.ValidationError(_(u'''\
+A user with that username already exists.'''))
+
+
+class RegistrationFormTermsOfService(RegistrationForm):
+ '''Subclass of :class:`RegistrationForm` which adds a required checkbox
+ for agreeing to a site's Terms of Service.
+ '''
+ tos = dforms.BooleanField(widget=Checkbox(attrs=forms.attrs_dict),
+ label=TERMS_LABEL,
+ error_messages={'required': TERMS_ERROR_MSG})
+
+
+class RegistrationFormUniqueEmail(RegistrationForm):
+ '''Subclass of :class:`RegistrationForm`, which enforces uniqueness of
+ email addresses.
+ '''
+ def clean_email(self):
+ '''Validate that the supplied email address is unique for the site.'''
+ session = orm.sessionmaker()
+ user_ct = session.query(User) \
+ .filter_by(email=self.cleaned_data['email']) \
+ .count()
+ if user_ct == 0:
+ return self.cleaned_data['email']
+ else:
+ raise dforms.ValidationError(_(u'''\
+This email address is already in use. Please supply a different email
+address.'''.replace('\n', ' ')))
+
+
+class RegistrationFormNoFreeEmail(RegistrationForm):
+ '''Subclass of :class:`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',
+ 'yahoo.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 dforms.ValidationError(_(u'''\
+Registration using free email addresses is prohibited. Please supply a
+different email address.'''.replace('\n', ' ')))
+ return self.cleaned_data['email']
0  baph/auth/registration/management/__init__.py
No changes.
0  baph/auth/registration/management/commands/__init__.py
No changes.
18 baph/auth/registration/management/commands/cleanupregistration.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+'''
+A management command which deletes expired accounts (e.g., accounts which
+signed up but never activated) from the database.
+
+Calls :func:`RegistrationProfile.delete_expired_users`, which
+contains the actual logic for determining which accounts are deleted.
+'''
+
+from baph.auth.registration.models import RegistrationProfile
+from django.core.management.base import NoArgsCommand
+
+
+class Command(NoArgsCommand):
+ help = "Delete expired user registrations from the database"
+
+ def handle_noargs(self, **options):
+ RegistrationProfile.delete_expired_users()
267 baph/auth/registration/models.py
@@ -0,0 +1,267 @@
+# -*- coding: utf-8 -*-
+'''\
+:mod:`baph.auth.registration.models` -- Registration-related SQLAlchemy Models
+==============================================================================
+'''
+
+from baph.auth.models import User, AUTH_USER_FIELD
+from baph.db.models import Model
+from baph.db.orm import ORM
+from baph.decorators.db import sqlalchemy_session
+from baph.utils.importing import import_attr
+render_to_string = import_attr(['coffin.shortcuts'], 'render_to_string')
+from datetime import datetime, timedelta
+from django.conf import settings
+from django.utils.hashcompat import sha_constructor
+import random
+import re
+from sqlalchemy import Column, ForeignKey, Integer, String
+from sqlalchemy.orm import relation
+
+orm = ORM.get()
+SHA1_RE = re.compile(r'^[a-f0-9]{40}$')
+
+
+class RegistrationProfile(orm.Base, Model):
+ '''A simple profile which stores an activation key for use during user
+ account registration.
+
+ While it is possible to use this model as the value of the
+ :setting:`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.
+
+ '''
+ __tablename__ = 'auth_registration_profile'
+
+ ACTIVATED = u"ALREADY_ACTIVATED"
+
+ user_id = Column(AUTH_USER_FIELD, ForeignKey(User.id), primary_key=True)
+ activation_key = Column(String(40), nullable=False)
+
+ user = relation(User)
+
+ def __unicode__(self):
+ return u'Registration information for %s' % self.user
+
+ def activation_key_expired(self):
+ '''Determine whether the user's activation 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 :attr:`ACTIVATED`. Re-activating is
+ not permitted, and so this method returns :const:`True` in this
+ case.
+
+ 2. Otherwise, the date the user signed up is incremented by
+ the number of days specified in the setting
+ :const:`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 :const:`True`.
+
+ :returns: :const:`True` if the key has expired, :const:`False`
+ otherwise.
+ :rtype: :class:`bool`
+ '''
+ expiration_date = timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS)
+ return self.activation_key == self.ACTIVATED or \
+ (self.user.date_joined + expiration_date <= datetime.now())
+ activation_key_expired.boolean = True
+
+ def send_activation_email(self, site):
+ '''Send an activation email to the user associated with this
+ ``RegistrationProfile``.
+
+ 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. 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.
+
+ These templates will each receive the following context
+ variables:
+
+ ``activation_key``
+ The activation key for the new account.
+
+ ``expiration_days``
+ The number of days remaining during which the account may
+ be activated.
+
+ ``site``
+ An object representing the site on which the user
+ registered; depending on whether :mod:`baph.sites` is installed,
+ this may be an instance of either :class:`baph.sites.models.Site`
+ (if the sites application is installed) or
+ :class:`baph.sites.models.RequestSite` (if not). Consult the
+ documentation for the Django/Baph sites framework for details
+ regarding these objects' interfaces.
+ '''
+ ctx_dict = {
+ 'activation_key': self.activation_key,
+ 'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS,
+ 'subject_prefix': settings.EMAIL_SUBJECT_PREFIX,
+ 'site': site,
+ }
+ subject = render_to_string('registration/activation_email_subject.txt',
+ ctx_dict)
+ # Email subject *must not* contain newlines
+ subject = ''.join(subject.splitlines())
+
+ message = render_to_string('registration/activation_email.txt',
+ ctx_dict)
+
+ self.user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
+
+ @classmethod
+ @sqlalchemy_session
+ def activate_user(cls, activation_key, session=None):
+ '''Validate an activation key and activate the corresponding
+ :class:`~baph.auth.models.User` if valid.
+
+ If the key is valid and has not expired, return the
+ :class:`~baph.auth.models.User` after activating.
+
+ If the key is not valid or has expired, return :const:`False`.
+
+ If the key is valid but the :class:`~baph.auth.models.User` is already
+ active, return :const:`False`.
+
+ To prevent reactivation of an account which has been
+ deactivated by site administrators, the activation key is
+ reset to the string constant :attr:`ACTIVATED` after successful
+ activation.
+
+ :param activation_key: The activation key of the user who will be
+ activated.
+ :type activation_key: :class:`str`
+ '''
+ # 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):
+ profile = session.query(cls) \
+ .filter_by(activation_key=activation_key) \
+ .first()
+ if not profile:
+ return False
+ if not profile.activation_key_expired():
+ user = profile.user
+ user.is_active = True
+ profile.activation_key = cls.ACTIVATED
+ session.commit()
+ return user
+ return False
+
+ @classmethod
+ @sqlalchemy_session
+ def create_inactive_user(cls, username, email, password, site,
+ send_email=True, session=None):
+ '''Create a new, inactive :class:`~baph.auth.models.User`, generate a
+ :class:`RegistrationProfile` and email its activation key to the
+ :class:`~baph.auth.models.User`.
+
+ :param username: The username of the user.
+ :type username: :class:`unicode`
+ :param email: The email address of the user.
+ :type email: :class:`str`
+ :param password: The password of the user.
+ :type password: :class:`unicode`
+ :param site: The site associated with the user.
+ :type site: :class:`baph.sites.models.Site` or
+ :class:`baph.sites.models.RequestSite`
+ :param send_email: Whether an activation email will be sent to the new
+ user.
+ :type send_email: :class:`bool`
+ :rtype: :class:`baph.auth.models.User`
+ '''
+ new_user = User.create_user(username, email, password,
+ session=session)
+ new_user.is_active = False
+ session.commit()
+
+ registration_profile = cls.create_profile(new_user, session=session)
+
+ if send_email:
+ registration_profile.send_activation_email(site)
+
+ return new_user
+
+ @classmethod
+ @sqlalchemy_session
+ def create_profile(cls, user, session=None):
+ '''Create a :class:`RegistrationProfile` for a given
+ :class:`~baph.auth.models.User`.
+
+ The activation key for the :class:`RegistrationProfile` will be a
+ SHA1 hash, generated from a combination of the
+ :class:`~baph.auth.models.User`'s username and a random salt.
+
+ :rtype: :class:`RegistrationProfile`
+ '''
+ salt = sha_constructor(str(random.random())).hexdigest()[:5]
+ username = user.username
+ if isinstance(username, unicode):
+ username = username.encode('utf-8')
+ activation_key = sha_constructor(salt + username).hexdigest()
+ profile = RegistrationProfile(user=user, activation_key=activation_key)
+ session.add(profile)
+ session.commit()
+ return profile
+
+ @classmethod
+ @sqlalchemy_session
+ def delete_expired_users(cls, session=None):
+ '''Remove expired instances of :class:`RegistrationProfile` and their
+ associated users.
+
+ Accounts to be deleted are identified by searching for
+ instances of :class:`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
+ :class:`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 :class:`RegistrationProfile`; an inactive ``User`` which
+ does not have an associated :class:`RegistrationProfile` will not
+ be deleted.
+ '''
+ for profile in session.query(cls).all():
+ if profile.activation_key_expired():
+ user = profile.user
+ if not user.is_active:
+ session.delete(user)
+ session.delete(profile)
+ session.commit()
9 baph/auth/registration/signals.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+
+from django.dispatch import Signal
+
+# A new user has registered.
+user_registered = Signal(providing_args=["user", "request"])
+
+# A user has activated his or her account.
+user_activated = Signal(providing_args=["user", "request"])
17 baph/auth/registration/views.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+'''Views which allow users to create and activate accounts.'''
+
+from baph.utils.importing import import_attr
+import inspect
+(activate, register) = \
+ import_attr(['registration.views'], ['activate', 'register'])
+
+# replace the Django imports, per
+# http://www.davidcramer.net/code/486/jinja2-and-django-registration.html
+(redirect, render_to_response) = \
+ import_attr(['coffin.shortcuts'], ['redirect', 'render_to_response'])
+RequestContext = import_attr(['coffin.template'], 'RequestContext')
+get_backend = import_attr(['registration.backends'], 'get_backend')
+
+exec inspect.getsource(activate)
+exec inspect.getsource(register)
11 baph/auth/urls.py
@@ -0,0 +1,11 @@
+from coffin.conf.urls.defaults import patterns
+
+urlpatterns = patterns('baph.auth.views',
+ (r'^login/$', 'login', {'SSL': True}),
+ (r'^logout/$', 'logout'),
+ (r'^password_reset/$', 'password_reset'),
+ (r'^password_reset/done/$', 'password_reset_done'),
+ (r'^reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',
+ 'password_reset_confirm', {'SSL': True}),
+ (r'^reset/done/$', 'password_reset_complete'),
+)
166 baph/auth/views.py
@@ -0,0 +1,166 @@
+# -*- coding: utf-8 -*-
+
+import inspect
+
+from baph.db.shortcuts import get_object_or_404
+from baph.sites.models import get_current_site
+from baph.utils.importing import import_attr
+render_to_response = import_attr(['coffin.shortcuts'], 'render_to_response')
+from coffin.template import RequestContext
+from django.conf import settings
+from django.contrib.auth import REDIRECT_FIELD_NAME
+from django.contrib.auth.forms import AuthenticationForm
+from django.contrib.auth.tokens import default_token_generator
+from django.contrib.auth.views import (
+ password_reset_complete, password_reset_done)
+from django.core.urlresolvers import reverse
+
+from django.http import Http404, HttpResponseRedirect
+from django.utils.http import base36_to_int
+from django.utils.translation import ugettext_lazy as _
+from django.views.decorators.cache import never_cache
+from django.views.decorators.csrf import csrf_protect
+import re
+from uuid import UUID
+# avoid shadowing
+from . import login as auth_login, logout as auth_logout
+from .forms import PasswordResetForm, SetPasswordForm
+from .models import User
+
+# fun with monkeypatching
+exec inspect.getsource(password_reset_done)
+exec inspect.getsource(password_reset_complete)
+
+
+@csrf_protect
+@never_cache
+def login(request, template_name='registration/login.html',
+ redirect_field_name=REDIRECT_FIELD_NAME,
+ authentication_form=AuthenticationForm):
+ '''Displays the login form and handles the login action.'''
+
+ redirect_to = request.REQUEST.get(redirect_field_name, '')
+
+ if request.method == "POST":
+ form = authentication_form(data=request.POST)
+ if form.is_valid():
+ # Light security check -- make sure redirect_to isn't garbage.
+ if not redirect_to or ' ' in redirect_to:
+ redirect_to = settings.LOGIN_REDIRECT_URL
+
+ # Heavier security check -- redirects to http://example.com should
+ # not be allowed, but things like /view/?param=http://example.com
+ # should be allowed. This regex checks if there is a '//' *before*
+ # a question mark.
+ elif '//' in redirect_to and re.match(r'[^\?]*//', redirect_to):
+ redirect_to = settings.LOGIN_REDIRECT_URL
+
+ # Okay, security checks complete. Log the user in.
+ auth_login(request, form.get_user())
+
+ if request.session.test_cookie_worked():
+ request.session.delete_test_cookie()
+
+ return HttpResponseRedirect(redirect_to)
+
+ else:
+ form = authentication_form(request)
+
+ request.session.set_test_cookie()
+
+ current_site = get_current_site(request)
+ return render_to_response(template_name, {
+ 'form': form,
+ redirect_field_name: redirect_to,
+ 'site': current_site,
+ 'site_name': current_site.name,
+ }, context_instance=RequestContext(request))
+
+
+def logout(request, next_page=None,
+ template_name='registration/logged_out.html',
+ redirect_field_name=REDIRECT_FIELD_NAME):
+ '''Logs out the user and displays 'You are logged out' message.'''
+ auth_logout(request)
+ if next_page is None:
+ redirect_to = request.REQUEST.get(redirect_field_name, '')
+ if redirect_to:
+ return HttpResponseRedirect(redirect_to)
+ else:
+ current_site = get_current_site(request)
+ return render_to_response(template_name, {
+ 'site': current_site,
+ 'site_name': current_site.name,
+ 'title': _('Logged out'),
+ }, context_instance=RequestContext(request))
+ else:
+ # Redirect to this page until the session has been cleared.
+ return HttpResponseRedirect(next_page or request.path)
+
+
+@csrf_protect
+def password_reset(request, is_admin_site=False,
+ template_name='registration/password_reset_form.html',
+ email_template_name=None,
+ password_reset_form=PasswordResetForm,
+ token_generator=default_token_generator,
+ post_reset_redirect=None):
+ if post_reset_redirect is None:
+ post_reset_redirect = reverse('baph.auth.views.password_reset_done')
+ if request.method == "POST":
+ form = password_reset_form(request.POST)
+ if form.is_valid():
+ if not email_template_name:
+ email_template_name = 'registration/password_reset_email.html'
+ opts = {}
+ opts['use_https'] = request.is_secure()
+ opts['token_generator'] = token_generator
+ opts['email_template_name'] = email_template_name
+ opts['request'] = request
+ if is_admin_site:
+ opts['domain_override'] = request.META['HTTP_HOST']
+ form.save(**opts)
+ return HttpResponseRedirect(post_reset_redirect)
+ else:
+ form = password_reset_form()
+ return render_to_response(template_name, {
+ 'form': form,
+ }, context_instance=RequestContext(request))
+
+
+def password_reset_confirm(request, uidb36=None, token=None,
+ template_name=None,
+ token_generator=default_token_generator,
+ set_password_form=SetPasswordForm,
+ post_reset_redirect=None):
+ '''View that checks the hash in a password reset link and presents a form
+ for entering a new password. Doesn't need ``csrf_protect`` since no-one can
+ guess the URL.
+ '''
+ assert uidb36 is not None and token is not None # checked by URLconf
+ if not template_name:
+ template_name = 'registration/password_reset_confirm.html'
+ if post_reset_redirect is None:
+ post_reset_redirect = reverse('baph.auth.views.password_reset_complete')
+ try:
+ uid = UUID(int=base36_to_int(uidb36))
+ except ValueError:
+ raise Http404
+
+ user = get_object_or_404(User, id=uid)
+ context_instance = RequestContext(request)
+
+ if token_generator.check_token(user, token):
+ context_instance['validlink'] = True
+ if request.method == 'POST':
+ form = set_password_form(user, request.POST)
+ if form.is_valid():
+ form.save()
+ return HttpResponseRedirect(post_reset_redirect)
+ else:
+ form = set_password_form(None)
+ else:
+ context_instance['validlink'] = False
+ form = None
+ context_instance['form'] = form
+ return render_to_response(template_name, context_instance=context_instance)
18 baph/context_processors/__init__.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+'''\
+:mod:`baph.context_processors` -- Template Context Processors
+=============================================================
+
+.. moduleauthor:: Mark Lee <markl@evomediagroup.com>
+'''
+
+
+def http(request):
+ '''A restricted set of variables from the
+ :class:`~django.http.HttpResponse` object.
+ '''
+ gzip = 'gzip' in request.META.get('HTTP_ACCEPT_ENCODING', '')
+ return {
+ 'gzip_acceptable': gzip,
+ 'is_secure': request.is_secure(),
+ }
0  baph/db/__init__.py
No changes.
0  baph/db/management/__init__.py
No changes.
0  baph/db/management/commands/__init__.py
No changes.
21 baph/db/management/commands/syncsqlalchemy.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from baph.db.orm import ORM
+from baph.utils.importing import import_any_module
+from django.conf import settings
+from django.core.management.base import NoArgsCommand
+
+orm = ORM.get()
+
+
+class Command(NoArgsCommand):
+ help = u'Create the database tables for all apps in INSTALLED_APPS ' \
+ u'whose models are written with SQLAlchemy and whose tables ' \
+ u'have not already been created.'
+ requires_model_validation = False
+
+ def handle_noargs(self, **kwargs):
+ for app in settings.INSTALLED_APPS:
+ import_any_module(['%s.models' % app], raise_error=False)
+ if len(orm.metadata.tables) > 0:
+ orm.metadata.create_all()
122 baph/db/models.py
@@ -0,0 +1,122 @@
+# -*- coding: utf-8 -*-
+'''\
+:mod:`baph.db.models` -- Base SQLAlchemy Models
+===============================================
+
+.. moduleauthor:: Mark Lee <markl@evomediagroup.com>
+
+'''
+
+from django.utils.encoding import smart_str, smart_unicode
+from sqlalchemy import String, Unicode, UnicodeText
+from unicodedata import bidirectional
+
+RTL_TYPES = [
+ 'R',
+ 'AL',
+ 'RLE',
+ 'RLO',
+ 'AN',
+]
+
+
+class Model(object):
+ '''Base object for all SQLAlchemy models. Makes sure that all string
+ values are properly converted into Unicode. This object assumes that
+ either the subclassed object also has a base class of
+ :attr:`baph.db.orm.ORM.Base`, or the :class:`~baph.db.orm.Mapify`
+ decorator was used on the object. This is because the ``__table__``
+ attribute is needed for the column type check to work properly.
+
+ :param \*\*kwargs: name-value pairs which set model properties.
+ '''
+
+ def __init__(self, **kwargs):
+ for name, value in kwargs.iteritems():
+ setattr(self, name, value)
+
+ @staticmethod
+ def _truncate_invalid_chars(value, length):
+ '''Safety check: make sure we aren't truncating within the boundaries
+ of a multibyte character. Also, add a LTR BOM if the last character
+ is RTL.
+ '''
+ value = smart_str(value)
+ if length:
+ value = value[:length]
+ valid = False
+ while not valid and len(value):
+ try:
+ test = value.decode('utf8')
+
+ # check for RTL encoding without order marker terminator
+ direction = bidirectional(test[-1])
+ if direction in RTL_TYPES:
+ # this is RTL, we need 3 bytes for the BOM
+ if len(value) > (length - 3):
+ # not enough room - keep chopping
+ raise ValueError('Not enough room to truncate')
+ else:
+ test += u'\u200e' # LTR BOM
+ return smart_str(test)
+ else:
+ valid = True
+ del test
+ except (UnicodeDecodeError, ValueError):
+ # chop a letter off the end and try again
+ value = value[:-1]
+ return value
+
+ def __setattr__(self, name, value):
+ column = getattr(self.__table__.c, name, None)
+ try:
+ length = column.type.length
+ except AttributeError:
+ length = None
+
+ if column is not None and value and not isinstance(value, int):
+ if isinstance(column.type, String):
+ value = smart_unicode(self._truncate_invalid_chars(value,
+ length))
+ if not isinstance(column.type, (Unicode, UnicodeText)):
+ value = value.encode('utf8')
+
+ super(Model, self).__setattr__(name, value)
+
+ def update(self, data):
+ '''Updates an SQLAlchemy model object's properties using a dictionary.
+
+ :param data: The data to update the object properties with.
+ :type data: :class:`dict`
+ '''
+ for key, value in data.iteritems():
+ if hasattr(self, key) and getattr(self, key) != value:
+ setattr(self, key, value)
+
+ def to_dict(self):
+ '''Creates a dictionary out of the column properties of the object.
+ This is needed because it's sometimes not possible to just use
+ :data:`__dict__`.
+
+ :rtype: :class:`dict`
+ '''
+ __dict__ = dict([(key, val) for key, val in self.__dict__.iteritems()
+ if not key.startswith('_sa_')])
+ if len(__dict__) == 0:
+ return dict([(col.name, getattr(self, col.name))
+ for col in self.__table__.c])
+ else:
+ return __dict__
+
+
+class CustomPropsModel(Model):
+ '''Subclassed Model for SQLAlchemy models which use custom descriptors.'''
+
+ def to_dict(self):
+ '''Overrides the :meth:`Model.to_dict` method because of the way
+ that inherited columns are handled.
+ '''
+ return dict([(x.key, getattr(self, x.key))
+ for x in self.__mapper__.iterate_properties
+ if not hasattr(x, 'primaryjoin') and \
+ not x.key.startswith('_')])
174 baph/db/orm.py
@@ -0,0 +1,174 @@
+# -*- coding: utf-8 -*-
+'''\
+:mod:`baph.db.orm` -- SQLAlchemy ORM Utilities
+==============================================
+
+.. moduleauthor:: Mark Lee <markl@evomediagroup.com>
+
+'''
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.utils.encoding import smart_str
+from sqlalchemy import create_engine, MetaData, Table
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import mapper, sessionmaker, scoped_session
+from sqlalchemy.pool import NullPool
+from sqlalchemy.sql.expression import Join
+import urllib
+from urlparse import urlunparse
+
+
+class ORM(object):
+ '''A wrapper class for dealing with the various aspects of SQLAlchemy.
+
+ :param str name: The database settings to use. Defaults to ``default``.
+
+ :ivar Base: The base class for declarative ORM objects.
+ :ivar engine: The SQLAlchemy engine.
+ :ivar metadata: The SQLAlchemy metadata object.
+ '''
+ _databases = {}
+
+ def __init__(self, name=None):
+ if not name:
+ name = 'default'
+ data = {}
+ if hasattr(settings, 'DATABASES') and \
+ name in settings.DATABASES:
+ data = settings.DATABASES[name]
+ if data.get('ENGINE', '') == '':
+ raise ImproperlyConfigured('''\
+The database ORM connection requires, at minimum, an engine type.''')
+ if '.' in data['ENGINE']:
+ data['ENGINE'] = data['ENGINE'].rsplit('.', 1)[-1]
+ self.engine = self._create_engine(data)
+ ro_values = dict([(k[9:], v) for k, v in data.iteritems()
+ if k.startswith('READONLY_')])
+ if len(ro_values):
+ ro_data = dict(data)
+ ro_data.update(ro_values)
+ self.readonly_engine = self._create_engine(ro_data)
+ self.metadata = MetaData(self.engine)
+ self.Base = declarative_base(metadata=self.metadata)
+ if hasattr(self, 'readonly_engine'):
+ self._readonly_sessionmaker = \
+ scoped_session(sessionmaker(bind=self.readonly_engine))
+ self._sessionmaker = scoped_session(sessionmaker(bind=self.engine))
+
+ @staticmethod
+ def _create_url(data):
+ netloc = ''
+ if data.get('USER', '') != '':
+ netloc = data['USER']
+ if data.get('PASSWORD', '') != '':
+ netloc += ':%s' % data['PASSWORD']
+ if data.get('HOST', '') != '':
+ netloc += '@%s' % data['HOST']
+ elif data.get('HOST', '') != '':
+ netloc = data['HOST']
+ url_parts = (data['ENGINE'], netloc, data.get('NAME', ''), '', '', '')
+ if url_parts == (data['ENGINE'], '', '', '', '', ''):
+ result = '%s://' % data['ENGINE']
+ elif 'NAME' in data and url_parts == (data['ENGINE'], '',
+ data['NAME'], '', '', ''):
+ name = urllib.quote(smart_str(data['NAME']))
+ result = '%s:///%s' % (data['ENGINE'], name)
+ else:
+ result = urlunparse(url_parts)
+ return result
+
+ @classmethod
+ def _create_engine(cls, data):
+ '''Creates an SQLAlchemy engine.'''
+ return create_engine(cls._create_url(data), convert_unicode=True,
+ encoding='utf8', poolclass=NullPool)
+
+ @classmethod
+ def get(cls, name=None):
+ '''Singleton method for the :class:`ORM` object.
+
+ :rtype: :class:`ORM`
+ '''
+ if not name:
+ name = 'default'
+ db = cls._databases.get(name)
+ if not db:
+ db = cls._databases[name] = ORM(name=name)
+ return db
+
+ def get_existing_table(self, name, *args, **kwargs):
+ '''Creates a Table object from an existing SQL table.
+
+ :param str name: The name of the table.
+ :param \*args: Extra arguments to pass to
+ :class:`~sqlalchemy.schema.Table`.
+ :param \*\*kwargs: Extra keyworded arguments to pass to
+ :class:`~sqlalchemy.schema.Table`.
+ :rtype: :class:`sqlalchemy.schema.Table`
+ '''
+ return Table(name, self.metadata, autoload=True, *args, **kwargs)
+
+ def sessionmaker(self, readonly=False, **kwargs):
+ '''Creates an SQLAlchemy session.
+
+ :param bool readonly: Whether to create a session using the read-only
+ settings.
+ :rtype: :class:`sqlalchemy.orm.session.Session`
+ '''
+ if readonly and hasattr(self, '_readonly_sessionmaker'):
+ maker = self._readonly_sessionmaker
+ else:
+ maker = self._sessionmaker
+ return maker(**kwargs)
+
+ def sessionmaker_remove(self):
+ '''See :meth:`sqlalchemy.orm.session.Session.remove`.'''
+ if hasattr(self, '_readonly_sessionmaker'):
+ self._readonly_sessionmaker.remove()
+ self._sessionmaker.remove()
+
+ def sessionmaker_close(self):
+ '''See :meth:`sqlalchemy.orm.session.Session.close`.'''
+ if hasattr(self, '_readonly_sessionmaker'):
+ self._readonly_sessionmaker.close()
+ self._sessionmaker.close()
+
+ def sessionmaker_rollback(self):
+ '''See :meth:`sqlalchemy.orm.session.Session.rollback`.'''
+ if hasattr(self, '_readonly_sessionmaker'):
+ self._readonly_sessionmaker.rollback()
+ self._sessionmaker.rollback()
+
+
+class Mapify(object):
+ '''Automatically maps an object to a database table.
+ Use as a decorator (Python >= 2.6 only).
+
+ :param orm: The ORM object associated with the table.
+ :type orm: :class:`ORM`
+ :param table: The table to associate with the object.
+ :type table: :class:`str` (table name),
+ :class:`sqlalchemy.sql.expression.Join`, or
+ :class:`sqlalchemy.schema.Table`
+ :param \*args: Extra arguments to pass to Table (or the mapper, if table
+ is not a string).
+ :param \*\*kwargs: Extra keyworded arguments to pass to Table (or the
+ mapper, if ``table`` is not a string).
+ '''
+
+ def __init__(self, orm, table, *args, **kwargs):
+ if isinstance(table, (Join, Table)):
+ self.table = table
+ self.args = args
+ self.kwargs = kwargs
+ else:
+ # assume table name
+ self.table = orm.get_existing_table(table, *args, **kwargs)
+ self.args = []
+ self.kwargs = {}
+
+ def __call__(self, obj):
+ obj.__mapper__ = mapper(obj, self.table, *self.args, **self.kwargs)
+ obj.__table__ = self.table
+ return obj
16 baph/db/shortcuts.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from baph.db.orm import ORM
+from django.http import Http404
+
+orm = ORM.get()
+
+
+def get_object_or_404(klass, **kwargs):
+ session = kwargs.get('_session', orm.sessionmaker())
+ result = session.query(klass) \
+ .filter_by(**kwargs) \
+ .first()
+ if result is None:
+ raise Http404
+ return result
62 baph/db/types.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+'''\
+:mod:`baph.db.types` -- Custom SQLAlchemy Types
+===============================================
+
+.. moduleauthor:: Mark Lee <markl@evomediagroup.com>
+'''
+
+from sqlalchemy import types
+from sqlalchemy.databases import mysql, postgresql
+import uuid
+
+
+class UUID(types.TypeDecorator):
+ '''Generic UUID column type for SQLAlchemy. Includes native support for
+ PostgreSQL and a MySQL-specific implementation, in addition to the
+ ``CHAR``-based fallback. Based on code from the following sources:
+
+ * http://blog.sadphaeton.com/2009/01/19/sqlalchemy-recipeuuid-column.html
+ * http://article.gmane.org/gmane.comp.python.sqlalchemy.user/24056
+ '''
+ impl = types.CHAR
+
+ def __init__(self):
+ # use the char's length here
+ super(UUID, self).__init__(length=36)
+
+ def load_dialect_impl(self, dialect):
+ if dialect.name == 'mysql':
+ return dialect.type_descriptor(mysql.MSBinary(16))
+ elif dialect.name in ('postgres', 'postgresql'):
+ return dialect.type_descriptor(postgresql.PGUuid())
+ else:
+ return dialect.type_descriptor(types.CHAR(self.impl.length))
+
+ def process_bind_param(self, value, dialect=None):
+ if value:
+ if isinstance(value, uuid.UUID):
+ if dialect:
+ if dialect.name == 'mysql':
+ return value.bytes
+ elif dialect.name in ('postgres', 'postgresql'):
+ return value
+ return str(value)
+ else:
+ raise ValueError('value %s is not a valid uuid.UUID' % value)
+ else:
+ return None
+
+ def process_result_value(self, value, dialect=None):
+ if value:
+ if dialect:
+ if dialect.name == 'mysql':
+ return uuid.UUID(bytes=value)
+ elif dialect.name in ('postgres', 'postgresql'):
+ return value
+ return uuid.UUID(value)
+ else:
+ return None
+
+ def is_mutable(self):
+ return False
0  baph/decorators/__init__.py
No changes.
30 baph/decorators/db.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+'''\
+:mod:`baph.decorators.db` -- Database-related Decorators
+========================================================
+
+.. moduleauthor:: Mark Lee <markl@evomediagroup.com>
+'''
+
+from baph.db.orm import ORM
+from functools import wraps
+
+orm = ORM.get()
+
+
+def sqlalchemy_session(f):
+ '''Decorator that automatically attaches a SQLAlchemy session to a
+ function.
+ '''
+
+ @wraps(f)
+ def _handler(*args, **kwargs):
+ if not kwargs.get('session'):
+ kwargs['session'] = orm.sessionmaker()
+ try:
+ return f(*args, **kwargs)
+ except Exception:
+ kwargs['session'].rollback()
+ raise
+
+ return _handler
134 baph/decorators/json.py
@@ -0,0 +1,134 @@
+# -*- coding: utf-8 -*-
+'''\
+:mod:`baph.decorators.json` -- JSON-related decorators
+======================================================
+
+.. moduleauthor:: Mark Lee <markl@evomediagroup.com>
+.. moduleauthor:: JT Thibault <jt@evomediagroup.com>
+'''
+
+from __future__ import absolute_import
+
+from baph.utils.importing import import_any_module, import_attr
+render_to_response = import_attr(['coffin.shortcuts'], 'render_to_response')
+from django.http import (
+ HttpResponse, HttpResponseRedirect, HttpResponseForbidden)
+RequestContext = import_attr(['django.template'], 'RequestContext')
+from functools import wraps
+json = import_any_module(['json', 'simplejson', 'django.utils.simplejson'])
+
+
+def data_to_json_response(data, **kwargs):
+ '''Takes any input and converts it to JSON, wraps it in an
+ :class:`~django.http.HttpResponse`, and sets the proper MIME type.
+
+ :param data: The data to be serialized.
+ :param \*\*kwargs: extra keyword parameters given to :func:`json.dumps`.
+ :rtype: :class:`~django.http.HttpResponse`
+ '''
+ return HttpResponse(json.dumps(data, **kwargs),
+ mimetype='application/json')
+
+
+def render_to_json(func, **json_kwargs):
+ '''A decorator that takes the return value of the given function and
+ converts it to JSON, wraps it in an :class:`~django.http.HttpResponse`,
+ and sets the proper MIME type.
+ '''
+
+ @wraps(func)
+ def handler(request, *args, **kwargs):
+ '''Creates the wrapped function/method.'''
+ return data_to_json_response(func(request, *args, **kwargs),
+ **json_kwargs)
+
+ return handler
+
+
+class JSONify(object):
+ '''Generic decorator that uses Django's
+ :meth:`~django.http.HttpRequest.is_ajax` request method to return either:
+
+ * A JSON dictionary containing a ``content`` key with the rendered data,
+ embedded in an :class:`~django.http.HttpResponse` object.
+ * An :class:`~django.http.HttpResponse`, using a template which wraps the
+ data in a given HTML file.
+
+ :param alternate_renderer: An alternate function which renders the content
+ into HTML and wraps it in an
+ :class:`~django.http.HttpResponse`.
+ Alternatively, if you specify a template name,
+ a default Jinja2-based renderer is used.
+ :type alternate_renderer: :func:`callable` or :class:`str`
+ :param method: Whether or not the wrapped function is actually a method.
+ :type method: :class:`bool`
+ :param \*\*kwargs: extra keyword parameters given to :func:`json.dumps`.
+ '''
+
+ def __init__(self, alternate_renderer=None, method=False, **kwargs):
+ if alternate_renderer is None:
+ self.renderer = self.render
+ elif isinstance(alternate_renderer, basestring):
+ # assume Jinja2 template
+ self.renderer = self.render_jinja
+ self.template = alternate_renderer
+ else:
+ self.renderer = alternate_renderer
+ self.method = method
+ self.json_kwargs = kwargs
+
+ def __call__(self, func):
+ '''Creates the wrapped function/method.'''
+
+ @wraps(func)
+ def func_handler(request, *args, **kwargs):
+ data = func(request, *args, **kwargs)
+ return self._handler(data, request)
+
+ @wraps(func)
+ def method_handler(method_self, request, *args, **kwargs):
+ data = func(method_self, request, *args, **kwargs)
+ return self._handler(data, request)
+
+ if self.method:
+ return method_handler
+ else:
+ return func_handler
+
+ def _handler(self, data, request):
+ if isinstance(data, basestring):
+ data = {
+ 'content': data,
+ }
+ elif isinstance(data, (HttpResponseRedirect, HttpResponseForbidden)):
+ return data
+ if not (isinstance(data, dict) and 'content' in data):
+ raise ValueError('''\
+Your view needs to return a string, a dict with a "content" key, or a 301/403
+HttpResponse object.''')
+
+ if request.is_ajax():
+ return data_to_json_response(data, **self.json_kwargs)
+ else:
+ return self.renderer(request, data)
+
+ def render(self, request, data):
+ '''The default renderer for the HTML content.
+
+ :type request: :class:`~django.http.HttpRequest`
+ :param data: The data returned from the wrapped function.
+ :type data: :class:`dict` (must have a ``content`` key)
+ :rtype: :class:`~django.http.HttpResponse`
+ '''
+ return HttpResponse(data['content'])
+
+ def render_jinja(self, request, data):
+ '''The default Jinja2 renderer.
+
+ :type request: :class:`~django.http.HttpRequest`
+ :param data: The data returned from the wrapped function.
+ :type data: :class:`dict`
+ :rtype: :class:`~django.http.HttpResponse`
+ '''
+ return render_to_response(self.template, data,
+ context_instance=RequestContext(request))
0  baph/localflavor/__init__.py
No changes.
284 baph/localflavor/generic/__init__.py
@@ -0,0 +1,284 @@
+# -*- coding: utf-8 -*-
+'''\
+:mod:`baph.localflavor.generic` -- Generic "Local Flavor" Add-on Extension
+==========================================================================
+
+Adapted from:
+http://code.djangoproject.com/ticket/5446/
+
+Attachment: country_and_language_fields_trunk.4.patch
+'''
+
+from django.conf import settings
+from django.core.exceptions import ValidationError
+from django.utils.translation import ugettext as _
+
+# Countries list - ISO 3166-1
+# http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
+
+COUNTRIES = (
+ ('AD', _('Andorra')),
+ ('AE', _('United Arab Emirates')),
+ ('AF', _('Afghanistan')),
+ ('AG', _('Antigua & Barbuda')),
+ ('AI', _('Anguilla')),
+ ('AL', _('Albania')),
+ ('AM', _('Armenia')),
+ ('AN', _('Netherlands Antilles')),
+ ('AO', _('Angola')),
+ ('AQ', _('Antarctica')),
+ ('AR', _('Argentina')),
+ ('AS', _('American Samoa')),
+ ('AT', _('Austria')),
+ ('AU', _('Australia')),
+ ('AW', _('Aruba')),
+ ('AZ', _('Azerbaijan')),
+ ('BA', _('Bosnia and Herzegovina')),
+ ('BB', _('Barbados')),
+ ('BD', _('Bangladesh')),
+ ('BE', _('Belgium')),
+ ('BF', _('Burkina Faso')),
+ ('BG', _('Bulgaria')),
+ ('BH', _('Bahrain')),
+ ('BI', _('Burundi')),
+ ('BJ', _('Benin')),
+ ('BM', _('Bermuda')),
+ ('BN', _('Brunei Darussalam')),
+ ('BO', _('Bolivia')),
+ ('BR', _('Brazil')),
+ ('BS', _('Bahama')),
+ ('BT', _('Bhutan')),
+ ('BV', _('Bouvet Island')),
+ ('BW', _('Botswana')),
+ ('BY', _('Belarus')),
+ ('BZ', _('Belize')),
+ ('CA', _('Canada')),
+ ('CC', _('Cocos (Keeling) Islands')),
+ ('CF', _('Central African Republic')),
+ ('CG', _('Congo')),
+ ('CH', _('Switzerland')),
+ ('CI', _('Ivory Coast')),
+ ('CK', _('Cook Iislands')),
+ ('CL', _('Chile')),
+ ('CM', _('Cameroon')),
+ ('CN', _('China')),
+ ('CO', _('Colombia')),
+ ('CR', _('Costa Rica')),
+ ('CU', _('Cuba')),
+ ('CV', _('Cape Verde')),
+ ('CX', _('Christmas Island')),
+ ('CY', _('Cyprus')),
+ ('CZ', _('Czech Republic')),
+ ('DE', _('Germany')),
+ ('DJ', _('Djibouti')),
+ ('DK', _('Denmark')),
+ ('DM', _('Dominica')),
+ ('DO', _('Dominican Republic')),
+ ('DZ', _('Algeria')),
+ ('EC', _('Ecuador')),
+ ('EE', _('Estonia')),
+ ('EG', _('Egypt')),
+ ('EH', _('Western Sahara')),
+ ('ER', _('Eritrea')),
+ ('ES', _('Spain')),
+ ('ET', _('Ethiopia')),
+ ('FI', _('Finland')),
+ ('FJ', _('Fiji')),
+ ('FK', _('Falkland Islands (Malvinas)')),
+ ('FM', _('Micronesia')),
+ ('FO', _('Faroe Islands')),
+ ('FR', _('France')),
+ ('FX', _('France, Metropolitan')),
+ ('GA', _('Gabon')),
+ ('GB', _('United Kingdom (Great Britain)')),
+ ('GD', _('Grenada')),
+ ('GE', _('Georgia')),
+ ('GF', _('French Guiana')),
+ ('GH', _('Ghana')),
+ ('GI', _('Gibraltar')),
+ ('GL', _('Greenland')),
+ ('GM', _('Gambia')),
+ ('GN', _('Guinea')),
+ ('GP', _('Guadeloupe')),
+ ('GQ', _('Equatorial Guinea')),
+ ('GR', _('Greece')),
+ ('GS', _('S.Georgia & S.Sandwich Islands')),
+ ('GT', _('Guatemala')),
+ ('GU', _('Guam')),
+ ('GW', _('Guinea-Bissau')),
+ ('GY', _('Guyana')),
+ ('HK', _('Hong Kong')),
+ ('HM', _('Heard & McDonald Islands')),
+ ('HN', _('Honduras')),
+ ('HR', _('Croatia')),
+ ('HT', _('Haiti')),
+ ('HU', _('Hungary')),
+ ('ID', _('Indonesia')),
+ ('IE', _('Ireland')),
+ ('IL', _('Israel')),
+ ('IN', _('India')),
+ ('IO', _('British Indian Ocean Territory')),
+ ('IQ', _('Iraq')),
+ ('IR', _('Islamic Republic of Iran')),
+ ('IS', _('Iceland')),
+ ('IT', _('Italy')),
+ ('JM', _('Jamaica')),
+ ('JO', _('Jordan')),
+ ('JP', _('Japan')),
+ ('KE', _('Kenya')),
+ ('KG', _('Kyrgyzstan')),
+ ('KH', _('Cambodia')),
+ ('KI', _('Kiribati')),
+ ('KM', _('Comoros')),
+ ('KN', _('St. Kitts and Nevis')),
+ ('KP', _('Korea, Dem. People\'s Republic of')),
+ ('KR', _('Korea, Republic of')),
+ ('KW', _('Kuwait')),
+ ('KY', _('Cayman Islands')),
+ ('KZ', _('Kazakhstan')),
+ ('LA', _('Lao People\'s Democratic Republic')),
+ ('LB', _('Lebanon')),
+ ('LC', _('Saint Lucia')),
+ ('LI', _('Liechtenstein')),