Anonymous avatar Anonymous committed 5eb5c64

layout design

Comments (0)

Files changed (77)

+June 2009
+=========
+
+* Add support for DATABASE_SUPPORTS_TRANSACTIONS parameter to enable tests
+  to succeed in newer Django revisions (> 10925). Patch contributed by
+  Andi Albrecht. Thanks :)
+
+May 2009
+========
+
+* Improved performance of repr() for BaseModel derived classes.
+* Added support for setting Reply-To when sending email.
+* The Django auth app is now disabled in the default settings.py. If you want
+  to use it in your application you will need to enable it (and dependencies
+  such as sessions) explicitly.
+
+April 2009
+==========
+
+* Modify the default settings.py so that SMTP email is not send by default.
+
+March 2009
+==========
+
+* Added a basic console (./managed.py console) that uses the remote_api to
+  allow command execution against the live application on appspot.com
+* Fixed compatibility issues with new Django releases.
+
+February 2009
+=============
+
+* Fixed compatibility with App Engine Python SDK 1.1.9 file restrictions.
+
+January 2009
+============
+
+* Make the use of django.contrib.auth completely optional.
+* Added initial support for placing the helper itself (appengine_django
+  directory) into a zipfile named appengine_django.zip in the top level
+  project directory. Note. That manage.py commands will not work when using
+  a zipped appengine_django.
+
+September 2008
+==============
+
+* Added support for loading Django from a file named django.zip in the top
+  level project directory.
+* Improved session backend contributed by Bernd Dorn of Lovely Systems.
+
+Tue 19 August 2008
+==================
+
+Support for versions of Django less than 1.0beta_1 has been removed. If you
+wish to continue using Django 0.96 you will need to stay with revision 53 of
+the Google App Engine Helper for Django.
+
+Wed 6 August 2008
+=================
+
+This is the last version of the Google App Engine Helper for Django that will
+support Django 0.96. Future development of the helper will be targetted for the
+upcoming 1.0 release of Django.
+
+* Improved SDK detection on Windows by looking at both the PATH variable that
+  may be set by the installer and using the win32api module (if available) to
+  look for the SDK in the default Program Files location.
+* Replaced the startapp command with a version that installs an App Engine
+  Compatible application skeleton. Patch contributed by Andi Albrecht.
+* Changed the default runserver port to 8000 to match standard Django
+  behaviour. Path contributed by Waldemar Kornewald.
+* Email server settings from the Django settings file are provided to the App
+  Engine Mail API. Patch contributed by Waldemar Kornewald.
+* Added support for the Django memcache cache backend. Patch contributed by
+  Jonca Rafal.
+* Added support for the Django session middle with db and cache backends for
+  Django 1.0alpha only. Patches contributed by Jonca Rafal and Waldemar
+  Kornewald.
+* Moved the Django compatible login_required decorator to the standard Django
+  location. Patch contributed by Andi Albrecht.
+* Replaced the Django ModelForm class with the App Engine ModelForm class.
+* Added a repr implementation for the BaseModel class.
+* Many minor improvements to increase robustness and avoid errors if portions
+  of Django are not present.
+
+Tue 20 May 2008
+===============
+
+* Added an App Engine compatible implementation of the Django authentication
+  framework. This only works for Users at the present time, Groups and
+  Permissions are not yet supported.
+
+  The patch for this functionality was supplied by Andi Albrecht.
+
+* Added equality and inequality comparision functions to BaseModel.
+
+  The patch for this functionality was supplied by Andi Albrecht.
+
+
+Fri 16 May 2008
+===============
+
+* Changed location of local SDK directory to '.google_appengine' instead of
+  'google_appengine' so that it is automatically ignored without neding to
+  modify skip_files in app.yaml. 'google_appengine' will continue to work for
+  the next few releases.
+
+* Major improvements to SDK location detection code. When running on Windows or
+  Mac OS with the SDK installed via the Google supplied installers the SDK will
+  be automatically detected and added to Python's path.
+
+* The helper now detects, warns and removes Context Processors that are not
+  compatible with Google App Engine.
+
+* The Django mail API is now monkey patched to allow email to be sent via the
+  App Engine Mail API using the standard Django mail functions. This code does
+  not yet support sending multipart / HTML emails.
+
+  The patch for this functionality was supplied by Ryan Gates.
+
+* Added 'update', 'rollback' and 'vacuum_indexes' from appcfg.py to the list of
+  commands available via manage.py when using Django 0.97. You will still have
+  to uses appcfg.py directly if you are using Django 0.96.
+
+* Improved the deserialization routines to deserialize parent references
+  without needing to instantiate the parent instance. This allows instances
+  with non-existant parents to be succesfully loaded.
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+Known Issues with the Google App Engine Helper for Django
+=========================================================
+
+* manage.py commands do not work when appengine_django.zip is used
+
+When running with the helper itself contained within a zipfile the additional
+and modified manage.py commands do not work. I suspect this is due to Django
+not appropriately accounting for the zipfile when trying to locate the
+replacement commands.
+
+
+There are no other known issues with the helper at this time. See the issue
+tracker at http://code.google.com/p/google-app-engine-django/issues/list for
+up to date information on bugs and feature requests.
+r := $(shell svnversion -nc .. | sed -e 's/^[^:]*://;s/[A-Za-z]//')
+ts := $(shell date +%s)
+tmpdir := $(shell mktemp -ud)
+pwd := $(shell pwd)
+
+release:
+	@rm -rf $(tmpdir)
+	@mkdir -p $(tmpdir)
+	@svn export . $(tmpdir)/appengine_helper_for_django
+	@echo "VERSION=$(r)\nTIMESTAMP=$(ts)\n" > \
+		$(tmpdir)/appengine_helper_for_django/VERSION
+	@rm $(tmpdir)/appengine_helper_for_django/Makefile
+	@cd $(tmpdir); find . | \
+		zip $(pwd)/appengine_helper_for_django-r$(r).zip -@ >/dev/null
+	@rm -rf $(tmpdir)
+GOOGLE APP ENGINE HELPER FOR DJANGO
+===================================
+
+This package provides a helper that eases the process of developing a Django
+project to run on the Google App Engine. The helper is structured as a Django
+application that should be installed inside your project. See below for
+detailed usage instructions.
+
+The helper provides the following functionality:
+
+  * The ability to use most manage.py commands
+    - Additional manage.py commands (update, rollback, and vacuum_indexes) that
+      operate identically to the appcfg.py equivalents.
+  * A BaseModel class that appears the same as the standard Django Model class.
+  * The ability to serialize and deserialize model instances to JSON, YAML and
+    XML.
+  * Access to Django's test framework with a test datastore and support for
+    fixtures.
+  * The ability to send email via the App Engine mail API using the standard
+    Django mail functions.
+  * An App Engine compatible implementation of the Django authentication
+    framework. Only users are supported at this time. Group and Permission
+    support is not implemented.
+  * Support for the Django memcache cache backend module.
+  * Support for the db and cache session backed modules.
+
+The helper is provided in the context of a blank Django project, very
+similar to what would be provided by the django-admin.py startproject command.
+This project contains minor customisations to manage.py and settings.py that
+demonstrate how to integrate the helper with a Django project.
+
+To use the helper you have two choices:
+
+1) Copy the entire project provided with the helper and modify it to bootstrap
+   your project.
+2) Copy the appengine_django application into your existing project and modify
+   the settings appropriately.
+
+Instructions for both cases are provided below.
+
+
+Obtaining the helper
+--------------------
+
+You can download the latest released version of the helper from the Google Code
+project at: http://code.google.com/p/google-app-engine-django
+
+The helper will be unzipped into a directory named appengine_helper_for_django.
+
+Alternatively you can check out the latest version of the helper directly from
+the SVN repository with the following command:
+
+svn co http://google-app-engine-django.googlecode.com/svn/trunk/ \
+  appengine_helper_for_django
+
+
+Required Software
+-----------------
+
+You will need the Google App Engine SDK and its dependencies installed on your
+computer. Additionally if you are developing on a Windows machine you will need
+to install the Python for Windows extensions from
+http://sourceforge.net/projects/pywin32/
+
+This version of the helper requires Django 1.0beta_1 or greater. If you would
+prefer to use the stable version of Django (0.96) that is bundled with the App
+Engine SDK then you can download revision 53 or earlier of the helper from the
+address in the previous section. You must place Django in a directory named
+django/ instead your project, or provide a django.zip file containing the
+zipped Django source. See below for details.
+
+
+Using the helper to bootstrap a new project
+-------------------------------------------
+
+1) Copy the appengine_helper_for_django directory to a new location named after
+   your project
+
+2) Edit the application line in app.yaml to match the name you registered your
+   application under in the Admin Console.
+
+3) If you have installed the Google App Engine SDK using the Windows or MacOS
+   installers provided by Google you may skip this step.
+
+   Create a symlink from the location of the extracted SDK zipfile to the
+   '.google_appengine' directory inside your project. E.g:
+
+   ln -s /home/me/google_appengine /home/me/myproject/.google_appengine
+
+   If you like to stay up to date see the 'SDK via SVN' section below for an
+   alternative setup.
+
+4) Download and copy Django into the django/ subdirectory of your project.
+   Alternatively you can create a zipfile of the Django code and place it at
+   django.zip inside your project. E.g.
+
+   /home/me/myproject/django          (directory method)
+   /home/me/myproject/django.zip      (zipfile method)
+
+5) Run manage.py to start a new application for your code:
+
+   python manage.py startapp myapplication
+
+6) Add your code!
+
+
+Installing the helper into an existing project
+----------------------------------------------
+
+1) Copy the appengine_django application from within the helper into your
+   project.
+
+2) Copy app.yaml and main.py from within the helper into your project.
+
+3) Edit the application line in app.yaml to match the name you registered your
+   application under in the Admin Console.
+
+4) Add the following two lines to the top of manage.py::
+
+       from appengine_django import InstallAppengineHelperForDjango
+       InstallAppengineHelperForDjango()
+
+5) If you have installed the Google App Engine SDK using the Windows or MacOS
+   installers provided by Google you may skip this step.
+
+   Create a symlink from the location of the extracted SDK zipfile to the
+   '.google_appengine' directory inside your project. E.g:
+
+   ln -s /home/me/google_appengine /home/me/myproject/.google_appengine
+
+   If you like to stay up to date see the 'SDK via SVN' section below for an
+   alternative setup.
+
+6) Ensure Django is available within your project as described in Step 4 of the
+   instructions for bootstrapping new porjects above.
+
+7) Remove incompatible settings from your settings.py file.
+
+   The helper can warn you about some settings that it knows to be
+   incompatible, you can see these warnings by running::
+
+       python manage.py diffsettings
+
+   For the rest you'll just have to experiment by trial and error. The main
+   problem here is usually loading middleware that attempts to import modules
+   that are banned by the appserver.
+
+8) Port your models and code over to the appengine_django.model.BaseModel
+   class.
+
+
+SDK via SVN
+-----------
+
+If you are using SVN to manage your project and you would like to keep up to
+date with the latest SDK without having to download and install it regularly
+you can use an svn:external to include the SDK in our project.
+
+Once you have commited the basic structure of your project to your repository
+change to the base directory and add the following line to the svn:externals
+property using svn propset or svn propedit.
+
+.google_appengine http://googleappengine.googlecode.com/svn/trunk/python/
+
+Then run svn update and a copy of the SDK will be installed into the
+'.google_appengine' subdirectory. This copy of the SDK will be updated with the
+latest changes from the Google Code repository every time you run svn update.
+
+
+Contributing to the helper
+--------------------------
+
+We would be happy to consider any additions or bugfixes that you would like to
+add to the helper. Please add them as a patch, in unified diff format to the
+Issue Tracker at: http://code.google.com/p/google-app-engine-django/issues/list
+
+Before we can accept your code you will need to have signed the Google
+Contributer License. You can find this at:
+
+http://code.google.com/legal/individual-cla-v1.0.html
+or
+http://code.google.com/legal/corporate-cla-v1.0.html
+
+If you are an Individual contributor you will be able to electronically sign
+and submit the form at the URL above. Please ensure that you use the same email
+address to submit your patch as you used to sign the CLA.
+
+
+Reporting Bugs and Requesting Features
+--------------------------------------
+
+Please see the KNOWN_ISSUES file and the existing list of issues at
+http://code.google.com/p/google-app-engine-django/issues to see if your problem
+has already been reported.
+
+If you find a bug or would like to request a feature you may do so at the
+Google Code issue tracker for this project:
+
+http://code.google.com/p/google-app-engine-django/issues/entry

Empty file added.

+application: meblog
+version: 1
+runtime: python
+api_version: 1
+
+handlers:
+- url: /static
+  static_dir: static
+
+- url: /.*
+  script: main.py

appengine_django/__init__.py

+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Support for integrating a Django project with the appengine infrastructure.
+
+This requires Django 1.0beta1 or greater.
+
+This module enables you to use the Django manage.py utility and *some* of it's
+subcommands. View the help of manage.py for exact details.
+
+Additionally this module takes care of initialising the datastore (and a test
+datastore) so that the Django test infrastructure can be used for your
+appengine project.
+
+To use this module add the following two lines to your main.py and manage.py
+scripts at the end of your imports:
+  from appengine_django import InstallAppengineHelperForDjango
+  InstallAppengineHelperForDjango()
+
+If you would like to use a version of Django other than that provided by the
+system all you need to do is include it in a directory just above this helper,
+eg:
+  appengine_django/__init__.py        -  This file
+  django/...                          - your private copy of Django.
+"""
+
+import logging
+import os
+import re
+import sys
+import unittest
+import zipfile
+
+
+DIR_PATH = os.path.abspath(os.path.dirname(__file__))
+PARENT_DIR = os.path.dirname(DIR_PATH)
+if PARENT_DIR.endswith(".zip"):
+  # Check for appengine_django itself being in a zipfile.
+  PARENT_DIR = os.path.dirname(PARENT_DIR)
+
+# Add this project to the start of sys path to enable direct imports.
+sys.path = [PARENT_DIR,] + sys.path
+
+# Look for a zipped copy of Django.
+have_django_zip = False
+django_zip_path = os.path.join(PARENT_DIR, 'django.zip')
+
+
+# Flags made available this module
+appid = None
+have_appserver = False
+
+# Hide everything other than the flags above and the install function.
+__all__ = ("appid", "have_appserver", "have_django_zip",
+           "django_zip_path", "InstallAppengineHelperForDjango")
+
+
+INCOMPATIBLE_COMMANDS = ["adminindex", "createcachetable", "dbshell",
+                         "inspectdb", "runfcgi", "syncdb", "validate"]
+
+
+def FixPython26Logging():
+  import logging
+  logging.logMultiprocessing = 0
+
+
+def LoadSdk():
+  # Try to import the appengine code from the system path.
+  try:
+    from google.appengine.api import apiproxy_stub_map
+  except ImportError, e:
+    # Hack to fix reports of import errors on Ubuntu 9.10.
+    if 'google' in sys.modules:
+      del sys.modules['google']
+    # Not on the system path. Build a list of alternative paths where it may be.
+    # First look within the project for a local copy, then look for where the Mac
+    # OS SDK installs it.
+    paths = [os.path.join(PARENT_DIR, '.google_appengine'),
+             os.path.join(PARENT_DIR, 'google_appengine'),
+             '/usr/local/google_appengine']
+    # Then if on windows, look for where the Windows SDK installed it.
+    for path in os.environ.get('PATH', '').split(';'):
+      path = path.rstrip('\\')
+      if path.endswith('google_appengine'):
+        paths.append(path)
+    try:
+      from win32com.shell import shell
+      from win32com.shell import shellcon
+      id_list = shell.SHGetSpecialFolderLocation(
+          0, shellcon.CSIDL_PROGRAM_FILES)
+      program_files = shell.SHGetPathFromIDList(id_list)
+      paths.append(os.path.join(program_files, 'Google',
+                                'google_appengine'))
+    except ImportError, e:
+      # Not windows.
+      pass
+    # Loop through all possible paths and look for the SDK dir.
+    SDK_PATH = None
+    for sdk_path in paths:
+      if os.path.exists(sdk_path):
+        SDK_PATH = os.path.realpath(sdk_path)
+        break
+    if SDK_PATH is None:
+      # The SDK could not be found in any known location.
+      sys.stderr.write("The Google App Engine SDK could not be found!\n")
+      sys.stderr.write("See README for installation instructions.\n")
+      sys.exit(1)
+    if SDK_PATH == os.path.join(PARENT_DIR, 'google_appengine'):
+      logging.warn('Loading the SDK from the \'google_appengine\' subdirectory '
+                   'is now deprecated!')
+      logging.warn('Please move the SDK to a subdirectory named '
+                   '\'.google_appengine\' instead.')
+      logging.warn('See README for further details.')
+    # Add the SDK and the libraries within it to the system path.
+    EXTRA_PATHS = [
+        SDK_PATH,
+        os.path.join(SDK_PATH, 'lib', 'antlr3'),
+        os.path.join(SDK_PATH, 'lib', 'django'),
+        os.path.join(SDK_PATH, 'lib', 'ipaddr'),
+        os.path.join(SDK_PATH, 'lib', 'webob'),
+        os.path.join(SDK_PATH, 'lib', 'yaml', 'lib'),
+        os.path.join(SDK_PATH, 'lib', 'fancy_urllib'),
+    ]
+    # Add SDK paths at the start of sys.path, but after the local directory which
+    # was added to the start of sys.path on line 50 above. The local directory
+    # must come first to allow the local imports to override the SDK and
+    # site-packages directories.
+    sys.path = sys.path[0:1] + EXTRA_PATHS + sys.path[1:]
+
+
+def LoadDjango(version=None):
+  global have_django_zip
+
+  from google.appengine.dist import use_library
+  from google.appengine.dist._library import UnacceptableVersionError
+
+  # Must set this env var *before* importing any more of Django.
+  os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
+
+  # If we have set a version explicitly, force that
+  if version:
+    use_library('django', version)
+    return
+
+  if os.path.exists(django_zip_path):
+    have_django_zip = True
+    sys.path.insert(1, django_zip_path)
+
+  # Remove the standard version of Django if a local copy has been provided.
+  if have_django_zip or os.path.exists(os.path.join(PARENT_DIR, 'django')):
+    for k in [k for k in sys.modules if k.startswith('django')]:
+      del sys.modules[k]
+    return
+
+  # If we aren't loading from a zip or local copy then try for whichever
+  # supported version is highest and installed
+  for check_version in ('1.1', '1.0'):
+    try:
+      use_library('django', check_version)
+      return
+    except UnacceptableVersionError:
+      pass
+
+  raise UnacceptableVersionError()
+
+
+def LoadAppengineEnvironment():
+  """Loads the appengine environment.
+
+  Returns:
+    This function has no return value, but it sets the following parameters on
+    this package:
+    - appid: The name of the application.
+    - have_appserver: Boolean parameter which is True if the code is being run
+        from within the appserver environment.
+  """
+  global appid, have_appserver
+  from google.appengine.api import yaml_errors
+  from google.appengine.api import apiproxy_stub_map
+
+  # Detect if we are running under an appserver.
+  have_appserver = False
+  stub = apiproxy_stub_map.apiproxy.GetStub("datastore_v3")
+  if stub:
+    have_appserver = True
+
+  # Load the application identifier.
+  if have_appserver:
+    appid = os.environ.get("APPLICATION_ID", "unknown")
+  else:
+    # Running as manage.py script, read from config file.
+    try:
+      from google.appengine.tools import dev_appserver
+      appconfig, unused_matcher = dev_appserver.LoadAppConfig(PARENT_DIR, {})
+      appid = appconfig.application
+    except (ImportError, yaml_errors.EventListenerYAMLError), e:
+      logging.warn("Could not read the Application ID from app.yaml. "
+                   "This may break things in unusual ways!")
+      # Something went wrong.
+      appid = "unknown"
+
+  logging.debug("Loading application '%s' %s an appserver" %
+                (appid, have_appserver and "with" or "without"))
+
+
+def InstallAppengineDatabaseBackend():
+  """Installs the appengine database backend into Django.
+
+  The appengine database lives in the db/ subdirectory of this package, but is
+  known as "appengine" to Django. This function installs the module where
+  Django expects to find its database backends.
+  """
+  from appengine_django import db
+  sys.modules['django.db.backends.appengine'] = db
+  logging.debug("Installed appengine database backend")
+
+
+def InstallGoogleMemcache():
+  """Installs the Google memcache into Django.
+
+  By default django tries to import standard memcache module.
+  Because appengine memcache is API compatible with Python memcache module,
+  we can trick Django to think it is installed and to use it.
+
+  Now you can use CACHE_BACKEND = 'memcached://' in settings.py. IP address
+  and port number are not required.
+  """
+  from google.appengine.api import memcache
+  sys.modules['memcache'] = memcache
+  logging.debug("Installed App Engine memcache backend")
+
+
+def InstallDjangoModuleReplacements():
+  """Replaces internal Django modules with App Engine compatible versions."""
+
+  # Replace the session module with a partial replacement overlay using
+  # __path__ so that portions not replaced will fall through to the original
+  # implementation.
+  try:
+    from django.contrib import sessions
+    orig_path = sessions.__path__[0]
+    sessions.__path__.insert(0, os.path.join(DIR_PATH, 'sessions'))
+    from django.contrib.sessions import backends
+    backends.__path__.append(os.path.join(orig_path, 'backends'))
+  except ImportError:
+    logging.debug("No Django session support available")
+
+  # Replace incompatible dispatchers.
+  import django.core.signals
+  import django.db
+  import django.dispatch.dispatcher
+
+  # Rollback occurs automatically on Google App Engine. Disable the Django
+  # rollback handler.
+  try:
+    # pre 1.0
+    from django.dispatch import errors
+    CheckedException = errors.DispatcherKeyError
+    def _disconnectSignal():
+      django.dispatch.dispatcher.disconnect(
+          django.db._rollback_on_exception,
+          django.core.signals.got_request_exception)
+  except ImportError:
+    CheckedException = KeyError
+    def _disconnectSignal():
+      django.core.signals.got_request_exception.disconnect(
+          django.db._rollback_on_exception)
+
+  try:
+    _disconnectSignal()
+  except CheckedException, e:
+    logging.debug("Django rollback handler appears to be already disabled.")
+
+
+def PatchDjangoSerializationModules(settings):
+  """Monkey patches the Django serialization modules.
+
+  The standard Django serialization modules to not correctly handle the
+  datastore models provided by this package. This method installs replacements
+  for selected modules and methods to give Django the capability to correctly
+  serialize and deserialize datastore models.
+  """
+  # These can't be imported until InstallAppengineDatabaseBackend has run.
+  from django.core.serializers import python
+  from appengine_django.serializer.python import Deserializer
+  if not hasattr(settings, "SERIALIZATION_MODULES"):
+    settings.SERIALIZATION_MODULES = {}
+  base_module = "appengine_django"
+  settings.SERIALIZATION_MODULES["xml"] = "%s.serializer.xml" % base_module
+  python.Deserializer = Deserializer
+  # This must be imported after the Deserializer has been mokey patched above.
+  from django.core.serializers import json
+  from appengine_django.serializer.json import DjangoJSONEncoder
+  json.DjangoJSONEncoder = DjangoJSONEncoder
+  from django.core.serializers import pyyaml
+  from appengine_django.serializer.pyyaml import DjangoSafeDumper
+  pyyaml.DjangoSafeDumper = DjangoSafeDumper
+  PatchDeserializedObjectClass()
+  DisableModelValidation()
+  logging.debug("Installed appengine json and python serialization modules")
+
+
+def PatchDeserializedObjectClass():
+  """Patches the DeserializedObject class.
+
+  The default implementation calls save directly on the django Model base
+  class to avoid pre-save handlers. The model class provided by this package
+  is not derived from the Django Model class and therefore must be called
+  directly.
+
+  Additionally we need to clear the internal _parent attribute as it may
+  contain a FakeParent class that is used to deserialize instances without
+  needing to load the parent instance itself. See the PythonDeserializer for
+  more details.
+  """
+  # This can't be imported until InstallAppengineDatabaseBackend has run.
+  from django.core.serializers import base
+  class NewDeserializedObject(base.DeserializedObject):
+    def save(self, save_m2m=True):
+      self.object.save()
+      self.object._parent = None
+  base.DeserializedObject = NewDeserializedObject
+  logging.debug("Replacement DeserializedObject class installed")
+
+
+def DisableModelValidation():
+  """Disables Django's model validation routines.
+
+  The model validation is primarily concerned with validating foreign key
+  references. There is no equivalent checking code for datastore References at
+  this time.
+
+  Validation needs to be disabled or serialization/deserialization will fail.
+  """
+  from django.core.management import validation
+  validation.get_validation_errors = lambda x, y=0: 0
+  logging.debug("Django SQL model validation disabled")
+
+
+def CleanupDjangoSettings(settings):
+  """Removes incompatible entries from the django settings module."""
+
+  # Ensure this module is installed as an application.
+  apps = getattr(settings, "INSTALLED_APPS", ())
+  found = False
+  for app in apps:
+    if app.endswith("appengine_django"):
+      found = True
+      break
+  if not found:
+    logging.warn("appengine_django module is not listed as an application!")
+    apps += ("appengine_django",)
+    setattr(settings, "INSTALLED_APPS", apps)
+    logging.info("Added 'appengine_django' as an application")
+
+  # Ensure the database backend is appropriately configured.
+  dbe = getattr(settings, "DATABASE_ENGINE", "")
+  if dbe != "appengine":
+    settings.DATABASE_ENGINE = "appengine"
+    logging.warn("DATABASE_ENGINE is not configured as 'appengine'. "
+                 "Value overriden!")
+  for var in ["NAME", "USER", "PASSWORD", "HOST", "PORT"]:
+    val = getattr(settings, "DATABASE_%s" % var, "")
+    if val:
+      setattr(settings, "DATABASE_%s" % var, "")
+      logging.warn("DATABASE_%s should be blank. Value overriden!")
+
+  # Remove incompatible middleware modules.
+  mw_mods = list(getattr(settings, "MIDDLEWARE_CLASSES", ()))
+  disallowed_middleware_mods = (
+    'django.middleware.doc.XViewMiddleware',)
+  for modname in mw_mods[:]:
+    if modname in disallowed_middleware_mods:
+      # Currently only the CommonMiddleware has been ported.  As other base
+      # modules are converted, remove from the disallowed_middleware_mods
+      # tuple.
+      mw_mods.remove(modname)
+      logging.warn("Middleware module '%s' is not compatible. Removed!" %
+                   modname)
+  setattr(settings, "MIDDLEWARE_CLASSES", tuple(mw_mods))
+
+  # Remove incompatible application modules
+  app_mods = list(getattr(settings, "INSTALLED_APPS", ()))
+  disallowed_apps = (
+    'django.contrib.contenttypes',
+    'django.contrib.sites',)
+  for app in app_mods[:]:
+    if app in disallowed_apps:
+      app_mods.remove(app)
+      logging.warn("Application module '%s' is not compatible. Removed!" % app)
+  setattr(settings, "INSTALLED_APPS", tuple(app_mods))
+
+  # Remove incompatible session backends.
+  session_backend = getattr(settings, "SESSION_ENGINE", "")
+  if session_backend.endswith("file"):
+    logging.warn("File session backend is not compatible. Overriden "
+                 "to use db backend!")
+    setattr(settings, "SESSION_ENGINE", "django.contrib.sessions.backends.db")
+
+
+def ModifyAvailableCommands():
+  """Removes incompatible commands and installs replacements where possible."""
+  if have_appserver:
+    # Commands are not used when running from an appserver.
+    return
+  from django.core import management
+  project_directory = os.path.join(__path__[0], "../")
+  if have_django_zip:
+    FindCommandsInZipfile.orig = management.find_commands
+    management.find_commands = FindCommandsInZipfile
+  management.get_commands()
+  # Replace startapp command which is set by previous call to get_commands().
+  from appengine_django.management.commands.startapp import ProjectCommand
+  management._commands['startapp'] = ProjectCommand(project_directory) 
+  RemoveCommands(management._commands)
+  logging.debug("Removed incompatible Django manage.py commands")
+
+
+def FindCommandsInZipfile(management_dir):
+    """
+    Given a path to a management directory, returns a list of all the command
+    names that are available.
+
+    This implementation also works when Django is loaded from a zip.
+
+    Returns an empty list if no commands are defined.
+    """
+    zip_marker = ".zip%s" % os.sep
+    if zip_marker not in management_dir:
+      return FindCommandsInZipfile.orig(management_dir)
+
+    # Django is sourced from a zipfile, ask zip module for a list of files.
+    filename, path = management_dir.split(zip_marker)
+    zipinfo = zipfile.ZipFile("%s.zip" % filename)
+
+    # Add commands directory to management path.
+    path = os.path.join(path, "commands")
+
+    # The zipfile module returns paths in the format of the operating system
+    # that created the zipfile! This may not match the path to the zipfile
+    # itself. Convert operating system specific characters to a standard
+    # character (#) to compare paths to work around this.
+    path_normalise = re.compile(r"[/\\]")
+    path = path_normalise.sub("#", path)
+    def _IsCmd(t):
+      """Returns true if t matches the criteria for a command module."""
+      filename = os.path.basename(t)
+      t = path_normalise.sub("#", t)
+      if not t.startswith(path):
+        return False
+      if filename.startswith("_") or not t.endswith(".py"):
+        return False
+      return True
+
+    return [os.path.basename(f)[:-3] for f in zipinfo.namelist() if _IsCmd(f)]
+
+
+def RemoveCommands(command_dict):
+  """Removes incompatible commands from the specified command dictionary."""
+  for cmd in command_dict.keys():
+    if cmd.startswith("sql"):
+      del command_dict[cmd]
+    elif cmd in INCOMPATIBLE_COMMANDS:
+      del command_dict[cmd]
+
+
+def InstallReplacementImpModule():
+  """Install a replacement for the imp module removed by the appserver.
+
+  This is only to find mangement modules provided by applications.
+  """
+  if not have_appserver:
+    return
+  modname = 'appengine_django.replacement_imp'
+  imp_mod = __import__(modname, {}, [], [''])
+  sys.modules['imp'] = imp_mod
+  logging.debug("Installed replacement imp module")
+
+
+def InstallReplacementThreadingModule():
+  """Install a replacement for the python threading module.
+
+  This is only to deal with a bug in Django 1.1+
+  """
+  try:
+    from django.utils._threading_local import local
+    import threading
+    threading.local = local
+  except ImportError:
+    # We are in Django 1.0
+    pass
+  logging.debug("Installed replacement threading module")
+
+
+def InstallAppengineHelperForDjango(version=None):
+  """Installs and Patches all of the classes/methods required for integration.
+
+  If the variable DEBUG_APPENGINE_DJANGO is set in the environment verbose
+  logging of the actions taken will be enabled.
+  """
+
+  FixPython26Logging()
+  LoadSdk()
+  LoadDjango(version)
+
+  from django import VERSION
+  from django.conf import settings
+
+  # Adding this again here to solve a problem that happens when context
+  # switching from webapp.template to django.template.
+  # TODO(elsigh): Maybe there is a deeper, fixable problem somewhere?
+  os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
+
+  if VERSION < (1, 0, None):
+    logging.error("Django 1.0 or greater is required!")
+    sys.exit(1)
+
+  if os.getenv("DEBUG_APPENGINE_DJANGO"):
+    logging.getLogger().setLevel(logging.DEBUG)
+  else:
+    logging.getLogger().setLevel(logging.INFO)
+  logging.debug("Loading the Google App Engine Helper for Django...")
+
+  # Force Django to reload its settings.
+  settings._target = None
+
+  LoadAppengineEnvironment()
+  InstallReplacementImpModule()
+  InstallReplacementThreadingModule()
+  InstallAppengineDatabaseBackend()
+  InstallModelForm()
+  InstallGoogleMemcache()
+  InstallDjangoModuleReplacements()
+  PatchDjangoSerializationModules(settings)
+  CleanupDjangoSettings(settings)
+  ModifyAvailableCommands()
+  InstallGoogleSMTPConnection()
+  InstallAuthentication(settings)
+
+  logging.debug("Successfully loaded the Google App Engine Helper for Django.")
+
+
+def InstallGoogleSMTPConnection():
+  from appengine_django import mail as gmail
+  from django.core import mail
+  logging.debug("Installing Google Email Adapter for Django")
+  mail.SMTPConnection = gmail.GoogleSMTPConnection
+  mail.mail_admins = gmail.mail_admins
+  mail.mail_managers = gmail.mail_managers
+
+
+def InstallAuthentication(settings):
+  if "django.contrib.auth" not in settings.INSTALLED_APPS:
+    return
+  try:
+    from appengine_django.auth import models as helper_models
+    from django.contrib.auth import models
+    models.User = helper_models.User
+    models.Group = helper_models.Group
+    models.Permission = helper_models.Permission
+    models.Message = helper_models.Message
+    from django.contrib.auth import middleware as django_middleware
+    from appengine_django.auth.middleware import AuthenticationMiddleware
+    django_middleware.AuthenticationMiddleware = AuthenticationMiddleware
+    from django.contrib.auth import decorators as django_decorators
+    from appengine_django.auth.decorators import login_required
+    django_decorators.login_required = login_required
+    from django.contrib import auth as django_auth
+    from django.contrib.auth import tests as django_tests
+    django_auth.suite = unittest.TestSuite
+    django_tests.suite = unittest.TestSuite
+    logging.debug("Installing authentication framework")
+  except ImportError:
+    logging.debug("No Django authentication support available")
+
+
+def InstallModelForm():
+  """Replace Django ModelForm with the AppEngine ModelForm."""
+  # This MUST happen as early as possible, but after any auth model patching.
+  from google.appengine.ext.db import djangoforms as aeforms
+  try:
+    # pre 1.0
+    from django import newforms as forms
+  except ImportError:
+    from django import forms
+
+  forms.ModelForm = aeforms.ModelForm
+
+  # Extend ModelForm with support for EmailProperty
+  # TODO: This should be submitted to the main App Engine SDK.
+  from google.appengine.ext.db import EmailProperty
+  def get_form_field(self, **kwargs):
+    """Return a Django form field appropriate for an email property."""
+    defaults = {'form_class': forms.EmailField}
+    defaults.update(kwargs)
+    return super(EmailProperty, self).get_form_field(**defaults)
+  EmailProperty.get_form_field = get_form_field
Add a comment to this file

appengine_django/__init__.pyc

Binary file added.

appengine_django/auth/__init__.py

+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Authentication module that mimics the behavior of Django's authentication
+implementation.
+
+Limitations:
+ - all user permissions methods are not available (requires contenttypes)
+"""
+
+from django.template import add_to_builtins
+
+add_to_builtins('appengine_django.auth.templatetags')

appengine_django/auth/decorators.py

+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Decorators for the authentication framework."""
+
+from django.http import HttpResponseRedirect
+
+from google.appengine.api import users
+
+
+def login_required(function):
+  """Implementation of Django's login_required decorator.
+  
+  The login redirect URL is always set to request.path
+  """
+  def login_required_wrapper(request, *args, **kw):
+    if request.user.is_authenticated():
+      return function(request, *args, **kw)
+    return HttpResponseRedirect(users.create_login_url(request.path))
+  return login_required_wrapper

appengine_django/auth/middleware.py

+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from django.contrib.auth.models import AnonymousUser
+
+from google.appengine.api import users
+
+from appengine_django.auth.models import User
+
+
+class LazyUser(object):
+  def __get__(self, request, obj_type=None):
+    if not hasattr(request, '_cached_user'):
+      user = users.get_current_user()
+      if user:
+        request._cached_user = User.get_djangouser_for_user(user)
+      else:
+        request._cached_user = AnonymousUser()
+    return request._cached_user
+
+
+class AuthenticationMiddleware(object):
+  def process_request(self, request):
+    request.__class__.user = LazyUser()
+    return None

appengine_django/auth/models.py

+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+App Engine compatible models for the Django authentication framework.
+"""
+
+from django.core import mail
+from django.core.exceptions import ImproperlyConfigured
+from django.db import models
+from django.utils.encoding import smart_str
+import urllib
+
+from django.db.models.manager import EmptyManager
+
+from google.appengine.api import users
+from google.appengine.ext import db
+
+from appengine_django.models import BaseModel
+
+
+class User(BaseModel):
+  """A model with the same attributes and methods as a Django user model.
+
+  The model has two additions. The first addition is a 'user' attribute which
+  references a App Engine user. The second is the 'get_djangouser_for_user'
+  classmethod that should be used to retrieve a DjangoUser instance from a App
+  Engine user object.
+  """
+  user = db.UserProperty(required=True)
+  username = db.StringProperty(required=True)
+  first_name = db.StringProperty()
+  last_name = db.StringProperty()
+  email = db.EmailProperty()
+  password = db.StringProperty()
+  is_staff = db.BooleanProperty(default=False, required=True)
+  is_active = db.BooleanProperty(default=True, required=True)
+  is_superuser = db.BooleanProperty(default=False, required=True)
+  last_login = db.DateTimeProperty(auto_now_add=True, required=True)
+  date_joined = db.DateTimeProperty(auto_now_add=True, required=True)
+  groups = EmptyManager()
+  user_permissions = EmptyManager()
+
+  def __unicode__(self):
+    return self.username
+
+  def __str__(self):
+    return unicode(self).encode('utf-8')
+
+  @classmethod
+  def get_djangouser_for_user(cls, user):
+    django_user = cls.get_by_key_name(user.user_id())
+    if django_user:
+      return django_user
+
+    # Check to make sure there's no legacy User object before creating a new
+    # one (new style User objects use a key_name based on the user_id).
+    django_user = cls.all().filter('user =', user).get()
+    if django_user:
+      return django_user
+
+    return cls.get_or_insert(
+        key_name=user.user_id(), user=user, email=user.email(),
+        username=user.nickname())
+
+  def set_password(self, raw_password):
+    raise NotImplementedError
+
+  def check_password(self, raw_password):
+    raise NotImplementedError
+
+  def set_unusable_password(self):
+    raise NotImplementedError
+
+  def has_usable_password(self):
+    raise NotImplementedError
+
+  def get_group_permissions(self):
+    return self.user_permissions
+
+  def get_all_permissions(self):
+    return self.user_permissions
+
+  def has_perm(self, perm):
+    return False
+
+  def has_perms(self, perm_list):
+    return False
+
+  def has_module_perms(self, module):
+    return False
+
+  def get_and_delete_messages(self):
+    """Gets and deletes messages for this user"""
+    msgs = []
+    for msg in self.message_set:
+      msgs.append(msg)
+      msg.delete()
+    return msgs
+
+  def is_anonymous(self):
+    """Always return False"""
+    return False
+
+  def is_authenticated(self):
+    """Always return True"""
+    return True
+
+  def get_absolute_url(self):
+    return "/users/%s/" % urllib.quote(smart_str(self.username))
+
+  def get_full_name(self):
+    full_name = u'%s %s' % (self.first_name, self.last_name)
+    return full_name.strip()
+
+  def email_user(self, subject, message, from_email):
+    """Sends an email to this user.
+
+    According to the App Engine email API the from_email must be the
+    email address of a registered administrator for the application.
+    """
+    mail.send_mail(subject,
+                   message,
+                   from_email,
+                   [self.email])
+
+  def get_profile(self):
+    """
+    Returns site-specific profile for this user. Raises
+    SiteProfileNotAvailable if this site does not allow profiles.
+
+    When using the App Engine authentication framework, users are created
+    automatically.
+    """
+    from django.contrib.auth.models import SiteProfileNotAvailable
+    if not hasattr(self, '_profile_cache'):
+      from django.conf import settings
+      if not hasattr(settings, "AUTH_PROFILE_MODULE"):
+        raise SiteProfileNotAvailable
+      try:
+        app_label, model_name = settings.AUTH_PROFILE_MODULE.split('.')
+        model = models.get_model(app_label, model_name)
+        self._profile_cache = model.all().filter("user =", self).get()
+        if not self._profile_cache:
+          raise model.DoesNotExist
+      except (ImportError, ImproperlyConfigured):
+        raise SiteProfileNotAvailable
+    return self._profile_cache
+
+
+class Group(BaseModel):
+  """Group model not fully implemented yet."""
+  # TODO: Implement this model, requires contenttypes
+  name = db.StringProperty()
+  permissions = EmptyManager()
+
+
+class Message(BaseModel):
+  """User message model"""
+  user = db.ReferenceProperty(User)
+  message = db.TextProperty()
+
+
+class Permission(BaseModel):
+  """Permission model not fully implemented yet."""
+  # TODO: Implement this model, requires contenttypes
+  name = db.StringProperty()

appengine_django/auth/templatetags.py

+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Template tags for the auth module. These are inserted into Django as "built-in"
+tags so you do not need to use the load statement in your template to get
+access to them.
+"""
+
+from django.template import Library
+from django.template import Node
+
+from google.appengine.api import users
+
+
+class AuthLoginUrlsNode(Node):
+  """Template node that creates an App Engine login or logout URL.
+
+  If create_login_url is True the App Engine's login URL is rendered into
+  the template, otherwise the logout URL.
+  """
+  def __init__(self, create_login_url, redirect):
+    self.redirect = redirect
+    self.create_login_url = create_login_url
+
+  def render(self, context):
+    if self.create_login_url:
+      return users.create_login_url(self.redirect)
+    else:
+      return users.create_logout_url(self.redirect)
+
+
+def auth_login_urls(parser, token):
+  """Template tag registered as 'auth_login_url' and 'auth_logout_url'
+  when the module is imported.
+
+  Both tags take an optional argument that specifies the redirect URL and
+  defaults to '/'.
+  """
+  bits = list(token.split_contents())
+  if len(bits) == 2:
+    redirect = bits[1]
+  else:
+    redirect = "/"
+  login = bits[0] == "auth_login_url"
+  return AuthLoginUrlsNode(login, redirect)
+
+
+register = Library()
+register.tag("auth_login_url", auth_login_urls)
+register.tag("auth_logout_url", auth_login_urls)

appengine_django/auth/tests.py

+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+BASIC_TESTS = """
+>>> from google.appengine.api import users
+>>> from models import User, AnonymousUser
+>>> appengine_user = users.User("test@example.com")
+>>> django_user = User.get_djangouser_for_user(appengine_user)
+>>> django_user.email == appengine_user.email()
+True
+>>> django_user.username == appengine_user.nickname()
+True
+>>> django_user.user == appengine_user
+True
+
+>>> django_user.username = 'test2'
+>>> key = django_user.save()
+>>> django_user.username == 'test2'
+True
+
+>>> django_user2 = User.get_djangouser_for_user(appengine_user)
+>>> django_user2 == django_user
+True
+
+>>> django_user.is_authenticated()
+True
+>>> django_user.is_staff
+False
+>>> django_user.is_active
+True
+
+>>> a = AnonymousUser()
+>>> a.is_authenticated()
+False
+>>> a.is_staff
+False
+>>> a.is_active
+False
+>>> a.groups.all()
+[]
+>>> a.user_permissions.all()
+[]
+
+
+"""
+
+__test__ = {'BASIC_TESTS': BASIC_TESTS}
Add a comment to this file

appengine_django/conf/app_template/__init__.py

Empty file added.

appengine_django/conf/app_template/models.py

+from appengine_django.models import BaseModel
+from google.appengine.ext import db
+
+# Create your models here.

appengine_django/conf/app_template/views.py

+# Create your views here.

appengine_django/db/__init__.py

+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Explicitly set the name of this package to "appengine".
+#
+# The rationale for this is so that Django can refer to the database as
+# "appengine" even though at a filesystem level it appears as the "db" package
+# within the appengine_django package.
+__name__ = "appengine"
Add a comment to this file

appengine_django/db/__init__.pyc

Binary file added.

appengine_django/db/base.py

+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""This module looks after initialising the appengine api stubs."""
+
+import logging
+import os
+
+from appengine_django import appid
+from appengine_django import have_appserver
+from appengine_django.db.creation import DatabaseCreation
+
+
+from django.db.backends import BaseDatabaseWrapper
+from django.db.backends import BaseDatabaseFeatures
+from django.db.backends import BaseDatabaseOperations
+
+
+def get_datastore_paths():
+  """Returns a tuple with the path to the datastore and history file.
+
+  The datastore is stored in the same location as dev_appserver uses by
+  default, but the name is altered to be unique to this project so multiple
+  Django projects can be developed on the same machine in parallel.
+
+  Returns:
+    (datastore_path, history_path)
+  """
+  from google.appengine.tools import dev_appserver_main
+  datastore_path = dev_appserver_main.DEFAULT_ARGS['datastore_path']
+  history_path = dev_appserver_main.DEFAULT_ARGS['history_path']
+  datastore_path = datastore_path.replace("dev_appserver", "django_%s" % appid)
+  history_path = history_path.replace("dev_appserver", "django_%s" % appid)
+  return datastore_path, history_path
+
+
+def get_test_datastore_paths(inmemory=True):
+  """Returns a tuple with the path to the test datastore and history file.
+
+  If inmemory is true, (None, None) is returned to request an in-memory
+  datastore. If inmemory is false the path returned will be similar to the path
+  returned by get_datastore_paths but with a different name.
+
+  Returns:
+    (datastore_path, history_path)
+  """
+  if inmemory:
+    return None, None
+  datastore_path, history_path = get_datastore_paths()
+  datastore_path = datastore_path.replace("datastore", "testdatastore")
+  history_path = history_path.replace("datastore", "testdatastore")
+  return datastore_path, history_path
+
+
+def destroy_datastore(datastore_path, history_path):
+  """Destroys the appengine datastore at the specified paths."""
+  for path in [datastore_path, history_path]:
+    if not path: continue
+    try:
+      os.remove(path)
+    except OSError, e:
+      if e.errno != 2:
+        logging.error("Failed to clear datastore: %s" % e)
+
+
+class DatabaseError(Exception):
+  """Stub class for database errors. Required by Django"""
+  pass
+
+
+class IntegrityError(Exception):
+  """Stub class for database integrity errors. Required by Django"""
+  pass
+
+
+class DatabaseFeatures(BaseDatabaseFeatures):
+  """Stub class to provide the feaures member expected by Django"""
+  pass
+
+
+class DatabaseOperations(BaseDatabaseOperations):
+  """Stub class to provide the options member expected by Django"""
+  pass
+
+
+class DatabaseWrapper(BaseDatabaseWrapper):
+  """App Engine database definition for Django.
+
+  This "database" backend does not support any of the standard backend
+  operations. The only task that it performs is to setup the api stubs required
+  by the appengine libraries if they have not already been initialised by an
+  appserver.
+  """
+
+  def __init__(self, *args, **kwargs):
+    super(DatabaseWrapper, self).__init__(*args, **kwargs)
+    self.features = DatabaseFeatures()
+    self.ops = DatabaseOperations()
+    self.creation = DatabaseCreation(self)
+    self.use_test_datastore = kwargs.get("use_test_datastore", False)
+    self.test_datastore_inmemory = kwargs.get("test_datastore_inmemory", True)
+    if have_appserver:
+      return
+    self._setup_stubs()
+
+  def _get_paths(self):
+    if self.use_test_datastore:
+      return get_test_datastore_paths(self.test_datastore_inmemory)
+    else:
+      return get_datastore_paths()
+
+  def _setup_stubs(self):
+    # If this code is being run without an appserver (eg. via a django
+    # commandline flag) then setup a default stub environment.
+    from google.appengine.tools import dev_appserver_main
+    args = dev_appserver_main.DEFAULT_ARGS.copy()
+    args['datastore_path'], args['history_path'] = self._get_paths()
+    from google.appengine.tools import dev_appserver
+    dev_appserver.SetupStubs(appid, **args)
+    if self.use_test_datastore:
+      logging.debug("Configured API stubs for the test datastore")
+    else:
+      logging.debug("Configured API stubs for the development datastore")
+
+  def flush(self):
+    """Helper function to remove the current datastore and re-open the stubs"""
+    destroy_datastore(*self._get_paths())
+    self._setup_stubs()
+
+  def close(self):
+    pass
+
+  def _commit(self):
+    pass
+
+  def cursor(self, *args):
+    pass
Add a comment to this file

appengine_django/db/base.pyc

Binary file added.

appengine_django/db/creation.py

+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+
+from django.conf import settings
+from django.db.backends.creation import BaseDatabaseCreation
+
+
+class DatabaseCreation(BaseDatabaseCreation):
+
+  def create_test_db(self, *args, **kw):
+    """Destroys the test datastore. A new store will be recreated on demand"""
+    settings.DATABASE_SUPPORTS_TRANSACTIONS = False
+    self.destroy_test_db()
+    self.connection.use_test_datastore = True
+    self.connection.flush()
+
+
+  def destroy_test_db(self, *args, **kw):
+    """Destroys the test datastore files."""
+    from appengine_django.db.base import destroy_datastore
+    from appengine_django.db.base import get_test_datastore_paths
+    destroy_datastore(*get_test_datastore_paths())
+    logging.debug("Destroyed test datastore")
Add a comment to this file

appengine_django/db/creation.pyc

Binary file added.

appengine_django/mail.py

+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+This module replaces the Django mail implementation with a version that sends
+email via the mail API provided by Google App Engine.
+
+Multipart / HTML email is not yet supported.
+"""
+
+import logging
+
+from django.core import mail
+from django.core.mail import SMTPConnection
+from django.conf import settings
+
+from google.appengine.api import mail as gmail
+
+
+class GoogleSMTPConnection(SMTPConnection):
+  def __init__(self, host=None, port=None, username=None, password=None,
+               use_tls=None, fail_silently=False):
+    self.use_tls = (use_tls is not None) and use_tls or settings.EMAIL_USE_TLS
+    self.fail_silently = fail_silently
+    self.connection = None
+
+  def open(self):
+    self.connection = True
+
+  def close(self):
+    pass
+
+  def _send(self, email_message):
+    """A helper method that does the actual sending."""
+    if not email_message.to:
+      return False
+    try:
+      if (isinstance(email_message,gmail.EmailMessage)):
+        e = message
+      elif (isinstance(email_message,mail.EmailMessage)):
+        e = gmail.EmailMessage(sender=email_message.from_email,
+                               to=email_message.to,
+                               subject=email_message.subject,
+                               body=email_message.body)
+        if email_message.extra_headers.get('Reply-To', None):
+            e.reply_to = email_message.extra_headers['Reply-To']
+        if email_message.bcc:
+            e.bcc = list(email_message.bcc)
+        #TODO - add support for html messages and attachments...
+      e.send()
+    except:
+      if not self.fail_silently:
+          raise
+      return False
+    return True
+
+
+def mail_admins(subject, message, fail_silently=False):
+    """Sends a message to the admins, as defined by the ADMINS setting."""
+    _mail_group(settings.ADMINS, subject, message, fail_silently)
+
+
+def mail_managers(subject, message, fail_silently=False):
+    """Sends a message to the managers, as defined by the MANAGERS setting."""
+    _mail_group(settings.MANAGERS, subject, message, fail_silently)
+
+
+def _mail_group(group, subject, message, fail_silently=False):
+    """Sends a message to an administrative group."""
+    if group:
+      mail.send_mail(settings.EMAIL_SUBJECT_PREFIX + subject, message,
+                     settings.SERVER_EMAIL, [a[1] for a in group],
+                     fail_silently)
+      return
+    # If the group had no recipients defined, default to the App Engine admins.
+    try:
+      gmail.send_mail_to_admins(settings.SERVER_EMAIL,
+                                settings.EMAIL_SUBJECT_PREFIX + subject,
+                                message)
+    except:
+      if not fail_silently:
+        raise
Add a comment to this file

appengine_django/mail.pyc

Binary file added.

Add a comment to this file

appengine_django/management/__init__.py

Empty file added.

Add a comment to this file

appengine_django/management/commands/__init__.py

Empty file added.

appengine_django/management/commands/console.py

+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import code
+import getpass
+import os
+import sys
+
+from django.conf import settings
+from django.core.management.base import BaseCommand
+
+from google.appengine.ext.remote_api import remote_api_stub
+
+
+def auth_func():
+  return raw_input('Username:'), getpass.getpass('Password:')
+
+class Command(BaseCommand):
+  """ Start up an interactive console backed by your app using remote_api """
+  
+  help = 'Start up an interactive console backed by your app using remote_api.'
+
+  def run_from_argv(self, argv):
+    app_id = argv[2]
+    if len(argv) > 3:
+      host = argv[3]
+    else:
+      host = '%s.appspot.com' % app_id
+
+    remote_api_stub.ConfigureRemoteDatastore(app_id, 
+                                             '/remote_api',
+                                             auth_func,
+                                             host)
+      
+    code.interact('App Engine interactive console for %s' % (app_id,), 
+                  None,
+                  locals())

appengine_django/management/commands/flush.py

+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import os
+import sys
+
+from django.core.management.base import BaseCommand
+
+
+class Command(BaseCommand):
+    """Overrides the default Django flush command.
+    """
+    help = 'Clears the current datastore and loads the initial fixture data.'
+
+    def run_from_argv(self, argv):
+      from django.db import connection
+      connection.flush()
+      from django.core.management import call_command
+      call_command('loaddata', 'initial_data')
+
+    def handle(self, *args, **kwargs):
+      self.run_from_argv(None)

appengine_django/management/commands/reset.py

+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os
+import sys
+
+from django.core.management.base import BaseCommand
+
+
+class Command(BaseCommand):
+    """Overrides the default Django reset command.
+    """
+    help = 'Clears the current datastore.'
+
+    def run_from_argv(self, argv):
+      from django.db import connection
+      connection.flush()

appengine_django/management/commands/rollback.py

+#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import sys
+import logging
+