Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

first full commit, from recovered project

  • Loading branch information...
commit bdb71859ea6f9da67f89b633967d01d931cd4943 1 parent a5f1bba
Alex Ehlke authored
Showing with 7,371 additions and 0 deletions.
  1. +1 −0  .gitignore
  2. +9 −0 RECOVERY_TODO
  3. +8 −0 __init__.py
  4. BIN  __init__.pyc
  5. 0  apps/__init__.py
  6. BIN  apps/__init__.pyc
  7. 0  apps/about/__init__.py
  8. BIN  apps/about/__init__.pyc
  9. +3 −0  apps/about/models.py
  10. BIN  apps/about/models.pyc
  11. +12 −0 apps/about/urls.py
  12. BIN  apps/about/urls.pyc
  13. +1 −0  apps/about/views.py
  14. BIN  apps/about/views.pyc
  15. +11 −0 apps/dojango/README
  16. 0  apps/dojango/__init__.py
  17. BIN  apps/dojango/__init__.pyc
  18. +20 −0 apps/dojango/appengine/README
  19. 0  apps/dojango/appengine/__init__.py
  20. +51 −0 apps/dojango/appengine/dojo_serve.py
  21. +435 −0 apps/dojango/appengine/memcache_zipserve.py
  22. +31 −0 apps/dojango/bin/dojobuild.py
  23. 0  apps/dojango/conf/__init__.py
  24. BIN  apps/dojango/conf/__init__.pyc
  25. +98 −0 apps/dojango/conf/settings.py
  26. BIN  apps/dojango/conf/settings.pyc
  27. +25 −0 apps/dojango/context_processors.py
  28. BIN  apps/dojango/context_processors.pyc
  29. +146 −0 apps/dojango/decorators.py
  30. BIN  apps/dojango/decorators.pyc
  31. +4 −0 apps/dojango/forms/__init__.py
  32. +143 −0 apps/dojango/forms/fields.py
  33. +70 −0 apps/dojango/forms/formsets.py
  34. +203 −0 apps/dojango/forms/models.py
  35. +464 −0 apps/dojango/forms/widgets.py
  36. 0  apps/dojango/management/__init__.py
  37. BIN  apps/dojango/management/__init__.pyc
  38. 0  apps/dojango/management/commands/__init__.py
  39. +317 −0 apps/dojango/management/commands/dojobuild.py
  40. +105 −0 apps/dojango/management/commands/dojoload.py
  41. +18 −0 apps/dojango/media/dojango.profile.js
  42. +5 −0 apps/dojango/media/dojango/_base.js
  43. +39 −0 apps/dojango/media/dojango/dojango.js
  44. +75 −0 apps/dojango/media/dojango/form/Form.js
  45. +30 −0 apps/dojango/media/dojango/widget/ThumbnailPicker.js
  46. +112 −0 apps/dojango/media/dojango/widget/plugins/InsertImage.js
  47. +5 −0 apps/dojango/media/dojango/widget/resources/ThumbnailPicker.css
  48. +26 −0 apps/dojango/media/dojango_optimized.profile.js
  49. BIN  apps/dojango/media/images/arrowSmall.gif
  50. BIN  apps/dojango/media/images/dojango_logo.jpg
  51. BIN  apps/dojango/media/images/note.gif
  52. BIN  apps/dojango/media/images/tube.gif
  53. BIN  apps/dojango/media/images/tubeTall.gif
  54. +1 −0  apps/dojango/media/resources/blank.html
  55. +72 −0 apps/dojango/middleware.py
  56. +3 −0  apps/dojango/models.py
  57. BIN  apps/dojango/models.pyc
  58. 0  apps/dojango/templates/__init__.py
  59. 0  apps/dojango/templates/dojango/__init__.py
  60. +88 −0 apps/dojango/templates/dojango/base.html
  61. +15 −0 apps/dojango/templates/dojango/base_i18n.html
  62. +39 −0 apps/dojango/templates/dojango/include.html
  63. +14 −0 apps/dojango/templates/dojango/include_i18n.html
  64. +7 −0 apps/dojango/templates/dojango/json_iframe.html
  65. 0  apps/dojango/templates/dojango/templatetag/__init__.py
  66. +55 −0 apps/dojango/templates/dojango/templatetag/datagrid_disp.html
  67. +757 −0 apps/dojango/templates/dojango/test.html
  68. 0  apps/dojango/templatetags/__init__.py
  69. BIN  apps/dojango/templatetags/__init__.pyc
  70. +39 −0 apps/dojango/templatetags/dojango_base.py
  71. BIN  apps/dojango/templatetags/dojango_base.pyc
  72. +9 −0 apps/dojango/templatetags/dojango_filters.py
  73. BIN  apps/dojango/templatetags/dojango_filters.pyc
  74. +152 −0 apps/dojango/templatetags/dojango_grid.py
  75. +20 −0 apps/dojango/urls.py
  76. BIN  apps/dojango/urls.pyc
  77. +239 −0 apps/dojango/util/__init__.py
  78. BIN  apps/dojango/util/__init__.pyc
  79. +127 −0 apps/dojango/util/config.py
  80. BIN  apps/dojango/util/config.pyc
  81. +50 −0 apps/dojango/util/dojo_collector.py
  82. BIN  apps/dojango/util/dojo_collector.pyc
  83. +39 −0 apps/dojango/util/form.py
  84. BIN  apps/dojango/util/form.pyc
  85. +24 −0 apps/dojango/util/perms.py
  86. BIN  apps/dojango/util/perms.pyc
  87. +228 −0 apps/dojango/views.py
  88. BIN  apps/dojango/views.pyc
  89. +41 −0 apps/flashcards/__init__.py
  90. BIN  apps/flashcards/__init__.pyc
  91. +11 −0 apps/flashcards/admin.py
  92. BIN  apps/flashcards/admin.pyc
  93. +81 −0 apps/flashcards/forms.py
  94. BIN  apps/flashcards/forms.pyc
  95. +327 −0 apps/flashcards/media/flashcards/js/flashcards.js
  96. +357 −0 apps/flashcards/media/flashcards/js/reviews.js
  97. BIN  apps/flashcards/models/.cards.py.swp
  98. +77 −0 apps/flashcards/models/__init__.py
  99. BIN  apps/flashcards/models/__init__.pyc
  100. +339 −0 apps/flashcards/models/cards.py
  101. BIN  apps/flashcards/models/cards.pyc
  102. +34 −0 apps/flashcards/models/cardtemplates.py
  103. BIN  apps/flashcards/models/cardtemplates.pyc
  104. +71 −0 apps/flashcards/models/decks.py
  105. BIN  apps/flashcards/models/decks.pyc
  106. +43 −0 apps/flashcards/models/facts.py
  107. BIN  apps/flashcards/models/facts.pyc
  108. +73 −0 apps/flashcards/models/fields.py
  109. BIN  apps/flashcards/models/fields.pyc
  110. +335 −0 apps/flashcards/templates/flashcards/base.html
  111. +32 −0 apps/flashcards/templates/flashcards/deck_confirm_delete.html
  112. +36 −0 apps/flashcards/templates/flashcards/deck_form.html
  113. +15 −0 apps/flashcards/templates/flashcards/deck_list.html
  114. +12 −0 apps/flashcards/templates/flashcards/extra_head.html
  115. +117 −0 apps/flashcards/templates/flashcards/reviews.html
  116. +65 −0 apps/flashcards/urls.py
  117. BIN  apps/flashcards/urls.pyc
  118. +517 −0 apps/flashcards/views.py
  119. BIN  apps/flashcards/views.pyc
  120. 0  deploy/__init__.py
  121. BIN  deploy/__init__.pyc
  122. +36 −0 deploy/modpython.py
  123. BIN  deploy/modpython.pyc
  124. +18 −0 deploy/pinax.fcgi
  125. +22 −0 deploy/pinax.wsgi
  126. BIN  dev.db.old
  127. +29 −0 manage.py
  128. BIN  manage.pyc
  129. +17 −0 media/css/site_tabs.css
  130. +18 −0 requirements.txt
  131. BIN  settings.pyc
  132. +41 −0 templates/about/what_next.html
  133. +26 −0 templates/account/base.html
  134. +41 −0 templates/homepage.html
  135. +3 −0  templates/notification/base.html
  136. +41 −0 templates/site_base.html
  137. +8 −0 todo.txt
  138. +43 −0 urls.py
  139. BIN  urls.pyc
1  .gitignore
View
@@ -0,0 +1 @@
+*.prc
9 RECOVERY_TODO
View
@@ -0,0 +1,9 @@
+recovery todo:
+
+abstract models
+shared models
+
+shared deck templates
+
+algorithms
+
8 __init__.py
View
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+__about__ = """
+This project comes with the bare minimum set of applications and templates
+to get you started. It includes no extra tabs, only the profile and notices
+tabs are included by default. From here you can add any extra functionality
+and applications that you would like.
+"""
BIN  __init__.pyc
View
Binary file not shown
0  apps/__init__.py
View
No changes.
BIN  apps/__init__.pyc
View
Binary file not shown
0  apps/about/__init__.py
View
No changes.
BIN  apps/about/__init__.pyc
View
Binary file not shown
3  apps/about/models.py
View
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
BIN  apps/about/models.pyc
View
Binary file not shown
12 apps/about/urls.py
View
@@ -0,0 +1,12 @@
+from django.conf.urls.defaults import *
+from django.views.generic.simple import direct_to_template
+
+urlpatterns = patterns('',
+ url(r'^$', direct_to_template, {"template": "about/about.html"}, name="about"),
+
+ url(r'^terms/$', direct_to_template, {"template": "about/terms.html"}, name="terms"),
+ url(r'^privacy/$', direct_to_template, {"template": "about/privacy.html"}, name="privacy"),
+ url(r'^dmca/$', direct_to_template, {"template": "about/dmca.html"}, name="dmca"),
+
+ url(r'^what_next/$', direct_to_template, {"template": "about/what_next.html"}, name="what_next"),
+)
BIN  apps/about/urls.pyc
View
Binary file not shown
1  apps/about/views.py
View
@@ -0,0 +1 @@
+# Create your views here.
BIN  apps/about/views.pyc
View
Binary file not shown
11 apps/dojango/README
View
@@ -0,0 +1,11 @@
+Dojango is a reusable django application that helps you to use the client-side
+framework dojo within your django project. It provides capabilites to easily
+switch between several dojo versions and sources (e.g. aol, google, local) and
+delivers helping utilities, that makes the development of rich internet
+applications in combination with dojo more comfortable. Also it makes the building
+of your own packed dojo release easier. Another goal of this project is, that you
+can learn how you have to structure your html to use dojo within your projects.
+
+For further documentation go to:
+
+http://code.google.com/p/dojango/
0  apps/dojango/__init__.py
View
No changes.
BIN  apps/dojango/__init__.pyc
View
Binary file not shown
20 apps/dojango/appengine/README
View
@@ -0,0 +1,20 @@
+This directory contains some helpers for running dojango on appengine.
+
+memcache_zipserve.py:
+
+ Part of http://code.google.com/p/google-app-engine-samples/:
+ Using zipserve to serve the media-files. After the first use they'll be
+ cached in memcache. Modified to support last-modified-headers (so we have
+ a real CDN!)
+
+dojo_serve.py:
+
+ Helper for serving the whole dojo release folder, that holds the dojo
+ modules as zipfiles.
+ It can be used within app.yaml (AppEngine configuration file) like this:
+
+ - url: /dojango/media/release/.*
+ script: dojango/appengine/dojo_serve.py
+
+ Afterwards all zip-files within /dojango/media/release/DOJANGO_DOJO_VERSION/
+ will be served and cached.
0  apps/dojango/appengine/__init__.py
View
No changes.
51 apps/dojango/appengine/dojo_serve.py
View
@@ -0,0 +1,51 @@
+import os
+import wsgiref.handlers
+
+from dojango.appengine import memcache_zipserve
+
+from google.appengine.ext import webapp
+
+# setup the environment
+from common.appenginepatch.aecmd import setup_env
+setup_env(manage_py_env=True)
+from dojango.conf import settings
+
+# creating a handler structure for the zip-files within the release folder
+release_dir = '%s/release/%s' % (settings.BASE_MEDIA_ROOT, settings.DOJO_VERSION)
+handlers = []
+for zip_file in os.listdir(release_dir):
+ if zip_file.endswith(".zip"):
+ module = os.path.splitext(zip_file)[0]
+ handler = [os.path.join(release_dir, zip_file)]
+ handlers.append(handler)
+
+class FlushCache(webapp.RequestHandler):
+ """
+ Handler for flushing the whole memcache instance.
+ """
+ from google.appengine.ext.webapp.util import login_required
+ @login_required
+ def get(self):
+ from google.appengine.api import memcache
+ from google.appengine.api import users
+ if users.is_current_user_admin():
+ stats = memcache.get_stats()
+ memcache.flush_all()
+ self.response.out.write("Memcache successfully flushed!<br/>")
+ if stats:
+ self.response.out.write("<p>Memcache stats:</p><p>")
+ for key in stats.keys():
+ self.response.out.write("%s: %s<br/>" % (key, stats[key]))
+ self.response.out.write("</p>")
+
+def main():
+ application = webapp.WSGIApplication([
+ ('%s/%s/(.*)' % (settings.BUILD_MEDIA_URL, settings.DOJO_VERSION),
+ memcache_zipserve.create_handler(handlers, max_age=31536000)
+ ),
+ ('%s/_flushcache[/]{0,1}' % settings.BUILD_MEDIA_URL, FlushCache)
+ ], debug=False)
+ wsgiref.handlers.CGIHandler().run(application)
+
+if __name__ == '__main__':
+ main()
435 apps/dojango/appengine/memcache_zipserve.py
View
@@ -0,0 +1,435 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+"""A class to serve pages from zip files and use memcache for performance.
+
+This contains a class and a function to create an anonymous instance of the
+class to serve HTTP GET requests. Memcache is used to increase response speed
+and lower processing cycles used in serving. Credit to Guido van Rossum and
+his implementation of zipserve which served as a reference as I wrote this.
+
+NOTE: THIS FILE WAS MODIFIED TO SUPPORT CLIENT CACHING
+
+ MemcachedZipHandler: Class that serves request
+ create_handler: method to create instance of MemcachedZipHandler
+"""
+
+__author__ = 'j.c@google.com (Justin Mattson)'
+
+import email.Utils
+import datetime
+import logging
+import mimetypes
+import os
+import time
+import zipfile
+
+from google.appengine.api import memcache
+from google.appengine.ext import webapp
+from google.appengine.ext.webapp import util
+
+from django.utils.hashcompat import md5_constructor
+
+def create_handler(zip_files, max_age=None, public=None, client_caching=None):
+ """Factory method to create a MemcachedZipHandler instance.
+
+ Args:
+ zip_files: A list of file names, or a list of lists of file name, first
+ member of file mappings. See MemcachedZipHandler documentation for
+ more information about using the list of lists format
+ max_age: The maximum client-side cache lifetime
+ public: Whether this should be declared public in the client-side cache
+ Returns:
+ A MemcachedZipHandler wrapped in a pretty, anonymous bow for use with App
+ Engine
+
+ Raises:
+ ValueError: if the zip_files argument is not a list
+ """
+ # verify argument integrity. If the argument is passed in list format,
+ # convert it to list of lists format
+
+ if zip_files and type(zip_files).__name__ == 'list':
+ num_items = len(zip_files)
+ while num_items > 0:
+ if type(zip_files[num_items - 1]).__name__ != 'list':
+ zip_files[num_items - 1] = [zip_files[num_items-1]]
+ num_items -= 1
+ else:
+ raise ValueError('File name arguments must be a list')
+
+ class HandlerWrapper(MemcachedZipHandler):
+ """Simple wrapper for an instance of MemcachedZipHandler.
+
+ I'm still not sure why this is needed
+ """
+
+ def get(self, name):
+ self.zipfilenames = zip_files
+ if max_age is not None:
+ self.MAX_AGE = max_age
+ if public is not None:
+ self.PUBLIC = public
+ if client_caching is not None:
+ self.CLIENT_CACHING = client_caching
+ self.TrueGet(name)
+
+ return HandlerWrapper
+
+
+class CacheFile(object):
+ pass
+
+class MemcachedZipHandler(webapp.RequestHandler):
+ """Handles get requests for a given URL.
+
+ Serves a GET request from a series of zip files. As files are served they are
+ put into memcache, which is much faster than retreiving them from the zip
+ source file again. It also uses considerably fewer CPU cycles.
+ """
+ zipfile_cache = {} # class cache of source zip files
+ current_last_modified = None # where we save the current last modified datetime
+ current_etag = None # the current ETag of a file served
+ CLIENT_CACHING = True # is client caching enabled? (sending Last-Modified and ETag within response!)
+ MAX_AGE = 600 # max client-side cache lifetime
+ PUBLIC = True # public cache setting
+ CACHE_PREFIX = "cache://" # memcache key prefix for actual URLs
+ NEG_CACHE_PREFIX = "noncache://" # memcache key prefix for non-existant URL
+
+ def TrueGet(self, name):
+ """The top-level entry point to serving requests.
+
+ Called 'True' get because it does the work when called from the wrapper
+ class' get method
+
+ Args:
+ name: URL requested
+
+ Returns:
+ None
+ """
+ name = self.PreprocessUrl(name)
+
+ # see if we have the page in the memcache
+ resp_data = self.GetFromCache(name)
+ if resp_data is None:
+ logging.info('Cache miss for %s', name)
+ resp_data = self.GetFromNegativeCache(name)
+ if resp_data is None or resp_data == -1:
+ resp_data = self.GetFromStore(name)
+ # IF we have the file, put it in the memcache
+ # ELSE put it in the negative cache
+ if resp_data is not None:
+ self.StoreOrUpdateInCache(name, resp_data)
+ else:
+ logging.info('Adding %s to negative cache, serving 404', name)
+ self.StoreInNegativeCache(name)
+ self.Write404Error()
+ return
+ else:
+ self.Write404Error()
+ return
+
+ content_type, encoding = mimetypes.guess_type(name)
+ if content_type:
+ self.response.headers['Content-Type'] = content_type
+ self.current_last_modified = resp_data.lastmod
+ self.current_etag = resp_data.etag
+ self.SetCachingHeaders()
+ # if the received ETag matches
+ if resp_data.etag == self.request.headers.get('If-None-Match'):
+ self.error(304)
+ return
+ # if-modified-since was passed by the browser
+ if self.request.headers.has_key('If-Modified-Since'):
+ dt = self.request.headers.get('If-Modified-Since').split(';')[0]
+ modsince = datetime.datetime.strptime(dt, "%a, %d %b %Y %H:%M:%S %Z")
+ if modsince >= self.current_last_modified:
+ # The file is older than the cached copy (or exactly the same)
+ self.error(304)
+ return
+ self.response.out.write(resp_data.file)
+
+ def PreprocessUrl(self, name):
+ """Any preprocessing work on the URL when it comes it.
+
+ Put any work related to interpretting the incoming URL here. For example,
+ this is used to redirect requests for a directory to the index.html file
+ in that directory. Subclasses should override this method to do different
+ preprocessing.
+
+ Args:
+ name: The incoming URL
+
+ Returns:
+ The processed URL
+ """
+ if name[len(name) - 1:] == '/':
+ return "%s%s" % (name, 'index.html')
+ else:
+ return name
+
+ def GetFromStore(self, file_path):
+ """Retrieve file from zip files.
+
+ Get the file from the source, it must not have been in the memcache. If
+ possible, we'll use the zip file index to quickly locate where the file
+ should be found. (See MapToFileArchive documentation for assumptions about
+ file ordering.) If we don't have an index or don't find the file where the
+ index says we should, look through all the zip files to find it.
+
+ Args:
+ file_path: the file that we're looking for
+
+ Returns:
+ The contents of the requested file
+ """
+ resp_data = None
+ file_itr = iter(self.zipfilenames)
+
+ # check the index, if we have one, to see what archive the file is in
+ archive_name = self.MapFileToArchive(file_path)
+ if not archive_name:
+ archive_name = file_itr.next()[0]
+
+ while resp_data is None and archive_name:
+ zip_archive = self.LoadZipFile(archive_name)
+ if zip_archive:
+
+ # we expect some lookups will fail, and that's okay, 404s will deal
+ # with that
+ try:
+ resp_data = CacheFile()
+ info = os.stat(archive_name)
+ #lastmod = datetime.datetime.fromtimestamp(info[8])
+ lastmod = datetime.datetime(*zip_archive.getinfo(file_path).date_time)
+ resp_data.file = zip_archive.read(file_path)
+ resp_data.lastmod = lastmod
+ resp_data.etag = '"%s"' % md5_constructor(resp_data.file).hexdigest()
+ except (KeyError, RuntimeError), err:
+ # no op
+ x = False
+ resp_data = None
+ if resp_data is not None:
+ logging.info('%s read from %s', file_path, archive_name)
+
+ try:
+ archive_name = file_itr.next()[0]
+ except (StopIteration), err:
+ archive_name = False
+
+ return resp_data
+
+ def LoadZipFile(self, zipfilename):
+ """Convenience method to load zip file.
+
+ Just a convenience method to load the zip file from the data store. This is
+ useful if we ever want to change data stores and also as a means of
+ dependency injection for testing. This method will look at our file cache
+ first, and then load and cache the file if there's a cache miss
+
+ Args:
+ zipfilename: the name of the zip file to load
+
+ Returns:
+ The zip file requested, or None if there is an I/O error
+ """
+ zip_archive = None
+ zip_archive = self.zipfile_cache.get(zipfilename)
+ if zip_archive is None:
+ try:
+ zip_archive = zipfile.ZipFile(zipfilename)
+ self.zipfile_cache[zipfilename] = zip_archive
+ except (IOError, RuntimeError), err:
+ logging.error('Can\'t open zipfile %s, cause: %s' % (zipfilename,
+ err))
+ return zip_archive
+
+ def MapFileToArchive(self, file_path):
+ """Given a file name, determine what archive it should be in.
+
+ This method makes two critical assumptions.
+ (1) The zip files passed as an argument to the handler, if concatenated
+ in that same order, would result in a total ordering
+ of all the files. See (2) for ordering type.
+ (2) Upper case letters before lower case letters. The traversal of a
+ directory tree is depth first. A parent directory's files are added
+ before the files of any child directories
+
+ Args:
+ file_path: the file to be mapped to an archive
+
+ Returns:
+ The name of the archive where we expect the file to be
+ """
+ num_archives = len(self.zipfilenames)
+ while num_archives > 0:
+ target = self.zipfilenames[num_archives - 1]
+ if len(target) > 1:
+ if self.CompareFilenames(target[1], file_path) >= 0:
+ return target[0]
+ num_archives -= 1
+
+ return None
+
+ def CompareFilenames(self, file1, file2):
+ """Determines whether file1 is lexigraphically 'before' file2.
+
+ WARNING: This method assumes that paths are output in a depth-first,
+ with parent directories' files stored before childs'
+
+ We say that file1 is lexigraphically before file2 if the last non-matching
+ path segment of file1 is alphabetically before file2.
+
+ Args:
+ file1: the first file path
+ file2: the second file path
+
+ Returns:
+ A positive number if file1 is before file2
+ A negative number if file2 is before file1
+ 0 if filenames are the same
+ """
+ f1_segments = file1.split('/')
+ f2_segments = file2.split('/')
+
+ segment_ptr = 0
+ while (segment_ptr < len(f1_segments) and
+ segment_ptr < len(f2_segments) and
+ f1_segments[segment_ptr] == f2_segments[segment_ptr]):
+ segment_ptr += 1
+
+ if len(f1_segments) == len(f2_segments):
+
+ # we fell off the end, the paths much be the same
+ if segment_ptr == len(f1_segments):
+ return 0
+
+ # we didn't fall of the end, compare the segments where they differ
+ if f1_segments[segment_ptr] < f2_segments[segment_ptr]:
+ return 1
+ elif f1_segments[segment_ptr] > f2_segments[segment_ptr]:
+ return -1
+ else:
+ return 0
+
+ # the number of segments differs, we either mismatched comparing
+ # directories, or comparing a file to a directory
+ else:
+
+ # IF we were looking at the last segment of one of the paths,
+ # the one with fewer segments is first because files come before
+ # directories
+ # ELSE we just need to compare directory names
+ if (segment_ptr + 1 == len(f1_segments) or
+ segment_ptr + 1 == len(f2_segments)):
+ return len(f2_segments) - len(f1_segments)
+ else:
+ if f1_segments[segment_ptr] < f2_segments[segment_ptr]:
+ return 1
+ elif f1_segments[segment_ptr] > f2_segments[segment_ptr]:
+ return -1
+ else:
+ return 0
+
+ def SetCachingHeaders(self):
+ """Set caching headers for the request."""
+ max_age = self.MAX_AGE
+ self.response.headers['Expires'] = email.Utils.formatdate(
+ time.time() + max_age, usegmt=True)
+ cache_control = []
+ if self.PUBLIC:
+ cache_control.append('public')
+ cache_control.append('max-age=%d' % max_age)
+ self.response.headers['Cache-Control'] = ', '.join(cache_control)
+ # adding caching headers for the client
+ if self.CLIENT_CACHING:
+ if self.current_last_modified:
+ self.response.headers['Last-Modified'] = self.current_last_modified.strftime("%a, %d %b %Y %H:%M:%S GMT")
+ if self.current_etag:
+ self.response.headers['ETag'] = self.current_etag
+
+ def GetFromCache(self, filename):
+ """Get file from memcache, if available.
+
+ Args:
+ filename: The URL of the file to return
+
+ Returns:
+ The content of the file
+ """
+ return memcache.get("%s%s" % (self.CACHE_PREFIX, filename))
+
+ def StoreOrUpdateInCache(self, filename, data):
+ """Store data in the cache.
+
+ Store a piece of data in the memcache. Memcache has a maximum item size of
+ 1*10^6 bytes. If the data is too large, fail, but log the failure. Future
+ work will consider compressing the data before storing or chunking it
+
+ Args:
+ filename: the name of the file to store
+ data: the data of the file
+
+ Returns:
+ None
+ """
+ try:
+ if not memcache.add("%s%s" % (self.CACHE_PREFIX, filename), data):
+ memcache.replace("%s%s" % (self.CACHE_PREFIX, filename), data)
+ except (ValueError), err:
+ logging.warning("Data size too large to cache\n%s" % err)
+
+ def Write404Error(self):
+ """Ouptut a simple 404 response."""
+ self.error(404)
+ self.response.out.write('Error 404, file not found')
+
+ def StoreInNegativeCache(self, filename):
+ """If a non-existant URL is accessed, cache this result as well.
+
+ Future work should consider setting a maximum negative cache size to
+ prevent it from from negatively impacting the real cache.
+
+ Args:
+ filename: URL to add ot negative cache
+
+ Returns:
+ None
+ """
+ memcache.add("%s%s" % (self.NEG_CACHE_PREFIX, filename), -1)
+
+ def GetFromNegativeCache(self, filename):
+ """Retrieve from negative cache.
+
+ Args:
+ filename: URL to retreive
+
+ Returns:
+ The file contents if present in the negative cache.
+ """
+ return memcache.get("%s%s" % (self.NEG_CACHE_PREFIX, filename))
+
+
+def main():
+ application = webapp.WSGIApplication([('/([^/]+)/(.*)',
+ MemcachedZipHandler)])
+ util.run_wsgi_app(application)
+
+
+if __name__ == '__main__':
+ main()
31 apps/dojango/bin/dojobuild.py
View
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+# This is the alternate dojo build command so it can be used
+# with older versions of django (mainly because of AppEngine, it uses version 0.96)
+import os
+import sys
+from optparse import OptionParser
+
+def setup_environ():
+ # we assume, that dojango is installed within your django's project dir
+ project_directory = os.path.abspath(os.path.dirname(__file__)+'/../../')
+ settings_filename = "settings.py"
+ if not project_directory:
+ project_directory = os.getcwd()
+ project_name = os.path.basename(project_directory)
+ settings_name = os.path.splitext(settings_filename)[0]
+ sys.path.append(project_directory)
+ sys.path.append(os.path.abspath(project_directory + "/.."))
+ project_module = __import__(project_name, {}, {}, [''])
+ sys.path.pop()
+ # Set DJANGO_SETTINGS_MODULE appropriately.
+ os.environ['DJANGO_SETTINGS_MODULE'] = '%s.%s' % (project_name, settings_name)
+ return project_directory
+
+project_dir = setup_environ()
+from dojango.management.commands.dojobuild import Command
+
+if __name__ == "__main__":
+ my_build = Command()
+ parser = OptionParser(option_list=my_build.option_list)
+ options, args = parser.parse_args(sys.argv)
+ my_build.handle(*args[1:], **options.__dict__)
0  apps/dojango/conf/__init__.py
View
No changes.
BIN  apps/dojango/conf/__init__.pyc
View
Binary file not shown
98 apps/dojango/conf/settings.py
View
@@ -0,0 +1,98 @@
+import os
+from django.conf import settings
+
+DEBUG = getattr(settings, "DEBUG", False)
+DEFAULT_CHARSET = getattr(settings, 'DEFAULT_CHARSET', 'utf-8')
+
+DOJO_VERSION = getattr(settings, "DOJANGO_DOJO_VERSION", "1.3.1")
+DOJO_PROFILE = getattr(settings, "DOJANGO_DOJO_PROFILE", "google")
+
+BASE_MEDIA_URL = getattr(settings, "DOJANGO_BASE_MEDIA_URL", '/dojango/media')
+BUILD_MEDIA_URL = getattr(settings, "DOJANGO_BUILD_MEDIA_URL", '%s/release' % BASE_MEDIA_URL)
+BASE_MEDIA_ROOT = getattr(settings, "DOJANGO_BASE_MEDIA_ROOT", os.path.abspath(os.path.dirname(__file__)+'/../media/'))
+BASE_DOJO_ROOT = getattr(settings, "DOJANGO_BASE_DOJO_ROOT", BASE_MEDIA_ROOT + "/dojo")
+# as default the dijit theme folder is used
+DOJO_THEME_URL = getattr(settings, "DOJANGO_DOJO_THEME_URL", False)
+DOJO_THEME = getattr(settings, "DOJANGO_DOJO_THEME", "tundra")
+DOJO_DEBUG = getattr(settings, "DOJANGO_DOJO_DEBUG", DEBUG) # using the default django DEBUG setting
+DOJO_SECURE_JSON = getattr(settings, "DOJANGO_DOJO_SECURE_JSON", True) # if you are using dojo version < 1.2.0 you have set it to False
+CDN_USE_SSL = getattr(settings, "DOJANGO_CDN_USE_SSL", False) # is dojo served via https from google? doesn't work for aol!
+
+# set the urls for actual possible paths for dojo
+# one dojo profile must at least contain a path that defines the base url of a dojo installation
+# the following settings can be set for each dojo profile:
+# - base_url: where do the dojo files reside (without the version folder!)
+# - use_xd: use the crossdomain-build? used to build the correct filename (e.g. dojo.xd.js)
+# - versions: this list defines all possible versions that are available in the defined profile
+# - uncompressed: use the uncompressed version of dojo (dojo.xd.js.uncompressed.js)
+# - use_gfx: there is a special case, when using dojox.gfx from aol (see http://dev.aol.com/dojo)
+# - is_local: marks a profile being local. this is needed when using the dojo module loader
+# - is_local_build: profile being a locally builded version
+_aol_versions = ('0.9.0', '1.0.0', '1.0.2', '1.1.0', '1.1.1', '1.2.0', '1.2.3', '1.3', '1.3.0',)
+_aol_gfx_versions = ('0.9.0', '1.0.0', '1.0.2', '1.1.0', '1.1.1',)
+_google_versions = ('1.1.1', '1.2', '1.2.0', '1.2.3', '1.3', '1.3.0', '1.3.1', )
+DOJO_PROFILES = {
+ 'google': {'base_url':(CDN_USE_SSL and 'https' or 'http') + '://ajax.googleapis.com/ajax/libs/dojo', 'use_xd':True, 'versions':_google_versions}, # google just supports version >= 1.1.1
+ 'google_uncompressed': {'base_url':(CDN_USE_SSL and 'https' or 'http') + '://ajax.googleapis.com/ajax/libs/dojo', 'use_xd':True, 'uncompressed':True, 'versions':_google_versions},
+ 'aol': {'base_url':'http://o.aolcdn.com/dojo', 'use_xd':True, 'versions':_aol_versions},
+ 'aol_uncompressed': {'base_url':'http://o.aolcdn.com/dojo', 'use_xd':True, 'uncompressed':True, 'versions':_aol_versions},
+ 'aol_gfx': {'base_url':'http://o.aolcdn.com/dojo', 'use_xd':True, 'use_gfx':True, 'versions':_aol_gfx_versions},
+ 'aol_gfx-uncompressed': {'base_url':'http://o.aolcdn.com/dojo', 'use_xd':True, 'use_gfx':True, 'uncompressed':True, 'versions':_aol_gfx_versions},
+ 'local': {'base_url': '%(BASE_MEDIA_URL)s/dojo', 'is_local':True}, # we don't have a restriction on version names, name them as you like
+ 'local_release': {'base_url': '%(BUILD_MEDIA_URL)s', 'is_local':True, 'is_local_build':True}, # this will be available after the first dojo build!
+ 'local_release_uncompressed': {'base_url': '%(BUILD_MEDIA_URL)s', 'uncompressed':True, 'is_local':True, 'is_local_build':True} # same here
+}
+
+# we just want users to append/overwrite own profiles
+DOJO_PROFILES.update(getattr(settings, "DOJANGO_DOJO_PROFILES", {}))
+
+# =============================================================================================
+# =================================== NEEDED FOR DOJO BUILD ===================================
+# =============================================================================================
+# general doc: http://dojotoolkit.org/book/dojo-book-0-9/part-4-meta-dojo/package-system-and-custom-builds
+# see http://www.sitepen.com/blog/2008/04/02/dojo-mini-optimization-tricks-with-the-dojo-toolkit/ for details
+DOJO_BUILD_VERSION = getattr(settings, "DOJANGO_DOJO_BUILD_VERSION", '1.3.1')
+# this is the default build profile, that is used, when calling "./manage.py dojobuild"
+# "./manage.py dojobuild dojango" would would have the same effect
+DOJO_BUILD_PROFILE = getattr(settings, "DOJANGO_DOJO_BUILD_PROFILE", "dojango")
+# This dictionary defines your build profiles you can use within the custom command "./manage.py dojobuild
+# You can set your own build profile within the main settings.py of the project by defining a dictionary
+# DOJANGO_DOJO_BUILD_PROFILES, that sets the following key/value pairs for each defined profile name:
+# profile_file: which dojo profile file is used for the build (see dojango.profile.js how it must look like)
+# options: these are the options that are passed to the build command (see the dojo doc for details)
+# OPTIONAL SETTINGS (see DOJO_BUILD_PROFILES_DEFAULT):
+# base_root: in which directory will the dojo version be builded to?
+# used_src_version: which version should be used for the dojo build (e.g. 1.1.1)
+# build_version: what is the version name of the builded release (e.g. dojango1.1.1) - this option can be overwritten by the commandline parameter --build_version=...
+# minify_extreme_skip_files: a tupel of files/folders (each expressed as regular expression) that should be kept when doing a minify extreme (useful when you have several layers and don't want some files)
+# this tupel will be appended to the default folders/files that are skipped: see SKIP_FILES in management/commands/dojobuild.py
+DOJO_BUILD_PROFILES = {
+ 'dojango': {
+ 'profile_file': '%(BASE_MEDIA_ROOT)s/dojango.profile.js',
+ 'options': 'profile=dojango action=release optimize=shrinksafe.keepLines cssOptimize=comments.keepLines',
+ },
+ 'dojango_optimized': {
+ 'profile_file': '%(BASE_MEDIA_ROOT)s/dojango_optimized.profile.js',
+ 'options': 'profile=dojango_optimized action=release optimize=shrinksafe.keepLines cssOptimize=comments.keepLines',
+ 'build_version': '%(DOJO_BUILD_VERSION)sdojango-optimized-with-dojo',
+ },
+}
+
+# these defaults are mixed into each DOJO_BUILD_PROFILES element
+# but you can overwrite each attribute within your own build profile element
+# e.g. DOJANGO_BUILD_PROFILES = {'used_src_version': '1.2.2', ....}
+DOJO_BUILD_PROFILES_DEFAULT = getattr(settings, "DOJANGO_DOJO_BUILD_PROFILES_DEFAULT", {
+ # build the release in the media directory of dojango
+ # use a formatting string, so this can be set in the project's settings.py without getting the dojango settings
+ 'base_root': '%(BASE_MEDIA_ROOT)s/release',
+ 'used_src_version': '%(DOJO_BUILD_VERSION)s',
+ 'build_version': '%(DOJO_BUILD_VERSION)sdojango-with-dojo',
+})
+# TODO: we should also enable the already pre-delivered dojo default profiles
+
+# you can add/overwrite your own build profiles
+DOJO_BUILD_PROFILES.update(getattr(settings, "DOJANGO_DOJO_BUILD_PROFILES", {}))
+DOJO_BUILD_JAVA_EXEC = getattr(settings, 'DOJANGO_DOJO_BUILD_JAVA_EXEC', 'java')
+# a version string that must have the following form: '1.0.0', '1.2.1', ....
+# this setting is used witin the dojobuild, because the build process changed since version 1.2.0
+DOJO_BUILD_USED_VERSION = getattr(settings, 'DOJANGO_DOJO_BUILD_USED_VERSION', '1.3.1')
BIN  apps/dojango/conf/settings.pyc
View
Binary file not shown
25 apps/dojango/context_processors.py
View
@@ -0,0 +1,25 @@
+from dojango.util.config import Config
+
+def config(request):
+ '''Make several dojango constants available in the template, like:
+
+ {{ DOJANGO.DOJO_BASE_URL }}, {{ DOJANGO.DOJO_URL }}, ...
+
+ You can also use the templatetag 'set_dojango_context' in your templates.
+ Just set the following at the top of your template to set these context
+ contants:
+
+ If you want to use the default DOJANGO_DOJO_VERSION/DOJANGO_DOJO_PROFILE:
+
+ {% load dojango_base %}
+ {% set_dojango_context %}
+
+ Using a difernet profile set the following:
+
+ {% load dojango_base %}
+ {% set_dojango_context "google" "1.1.1" %}
+ '''
+ context_extras = {'DOJANGO': {}}
+ config = Config()
+ context_extras['DOJANGO'] = config.get_context_dict()
+ return context_extras
BIN  apps/dojango/context_processors.pyc
View
Binary file not shown
146 apps/dojango/decorators.py
View
@@ -0,0 +1,146 @@
+from django.http import HttpResponseServerError
+from django.utils import simplejson as json
+
+from util import to_json_response
+from util import to_dojo_data
+
+try:
+ from functools import wraps
+except ImportError:
+ from django.utils.functional import wraps # Python 2.3, 2.4 fallback.
+
+def expect_post_request(func):
+ """Allow only POST requests to come in, throw an exception otherwise.
+
+ This relieves from checking every time that the request is
+ really a POST request, which it should be when using this
+ decorator.
+ """
+ def _ret(*args, **kwargs):
+ ret = func(*args, **kwargs)
+ request = args[0]
+ if not request.method=='POST':
+ raise Exception('POST request expected.')
+ return ret
+ return _ret
+
+def add_request_getdict(func):
+ """Add the method getdict() to the request object.
+
+ This works just like getlist() only that it decodes any nested
+ JSON encoded object structure.
+ Since sending deep nested structures is not possible via
+ GET/POST by default, this enables it. Of course you need to
+ make sure that on the JavaScript side you are also sending
+ the data properly, which dojango.send() automatically does.
+ Example:
+ this is being sent:
+ one:1
+ two:{"three":3, "four":4}
+ using
+ request.POST.getdict('two')
+ returns a dict containing the values sent by the JavaScript.
+ """
+ def _ret(*args, **kwargs):
+ args[0].POST.__class__.getdict = __getdict
+ ret = func(*args, **kwargs)
+ return ret
+ return _ret
+
+def __getdict(self, key):
+ ret = self.get(key)
+ try:
+ ret = json.loads(ret)
+ except ValueError: # The value was not JSON encoded :-)
+ raise Exception('"%s" was not JSON encoded as expected (%s).' % (key, str(ret)))
+ return ret
+
+def json_response(func):
+ """
+ A simple json response decorator. Use it on views, where a python data object should be converted
+ to a json response:
+
+ @json_response
+ def my_view(request):
+ my_data = {'foo': 'bar'}
+ return my_data
+ """
+ def inner(request, *args, **kwargs):
+ ret = func(request, *args, **kwargs)
+ return __prepare_json_ret(request, ret)
+ return wraps(func)(inner)
+
+def jsonp_response_custom(callback_param_name):
+ """
+ A jsonp (JSON with Padding) response decorator, where you can define your own callbackParamName.
+ It acts like the json_response decorator but with the difference, that it
+ wraps the returned json string into a client-specified function name (that is the Padding).
+
+ You can add this decorator to a function like that:
+
+ @jsonp_response_custom("my_callback_param")
+ def my_view(request):
+ my_data = {'foo': 'bar'}
+ return my_data
+
+ Your now can access this view from a foreign URL using JSONP.
+ An example with Dojo looks like that:
+
+ dojo.io.script.get({ url:"http://example.com/my_url/",
+ callbackParamName:"my_callback_param",
+ load: function(response){
+ console.log(response);
+ }
+ });
+
+ Note: the callback_param_name in the decorator and in your JavaScript JSONP call must be the same.
+ """
+ def decorator(func):
+ def inner(request, *args, **kwargs):
+ ret = func(request, *args, **kwargs)
+ return __prepare_json_ret(request, ret, callback_param_name=callback_param_name)
+ return wraps(func)(inner)
+ return decorator
+
+jsonp_response = jsonp_response_custom("jsonp_callback")
+jsonp_response.__doc__ = "A predefined jsonp response decorator using 'jsoncallback' as a fixed callback_param_name."
+
+def json_iframe_response(func):
+ """
+ A simple json response decorator but wrapping the json response into a html page.
+ It helps when doing a json request using an iframe (e.g. file up-/download):
+
+ @json_iframe
+ def my_view(request):
+ my_data = {'foo': 'bar'}
+ return my_data
+ """
+ def inner(request, *args, **kwargs):
+ ret = func(request, *args, **kwargs)
+ return __prepare_json_ret(request, ret, use_iframe=True)
+ return wraps(func)(inner)
+
+def __prepare_json_ret(request, ret, callback_param_name=None, use_iframe=False):
+ if ret==False:
+ ret = {'success':False}
+ elif ret==None: # Sometimes there is no return.
+ ret = {}
+ # Add the 'ret'=True, since it was obviously no set yet and we got valid data, no exception.
+ func_name = None
+ if callback_param_name:
+ func_name = request.GET.get(callback_param_name, "callbackParamName")
+ try:
+ if not ret.has_key('success'):
+ ret['success'] = True
+ except AttributeError, e:
+ raise Exception("The returned data of your function must be a dictionary!")
+ json_ret = ""
+ try:
+ # Sometimes the serialization fails, i.e. when there are too deeply nested objects or even classes inside
+ json_ret = to_json_response(ret, func_name, use_iframe)
+ except Exception, e:
+ print '\n\n===============Exception=============\n\n'+str(e)+'\n\n'
+ print ret
+ print '\n\n'
+ return HttpResponseServerError(content=str(e))
+ return json_ret
BIN  apps/dojango/decorators.pyc
View
Binary file not shown
4 apps/dojango/forms/__init__.py
View
@@ -0,0 +1,4 @@
+from django.forms import *
+from widgets import *
+from fields import *
+from models import *
143 apps/dojango/forms/fields.py
View
@@ -0,0 +1,143 @@
+from django.forms import *
+
+from dojango.forms import widgets
+from dojango.util import json_encode
+
+__all__ = (
+ 'Field', 'DEFAULT_DATE_INPUT_FORMATS', 'DEFAULT_TIME_INPUT_FORMATS', # original django classes
+ 'DEFAULT_DATETIME_INPUT_FORMATS', 'MultiValueField', 'ComboField', # original django classes
+ 'DojoFieldMixin', 'CharField', 'ChoiceField', 'TypedChoiceField',
+ 'IntegerField', 'BooleanField', 'FileField', 'ImageField',
+ 'DateField', 'TimeField', 'DateTimeField', 'SplitDateTimeField',
+ 'RegexField', 'DecimalField', 'FloatField', 'FilePathField',
+ 'MultipleChoiceField', 'NullBooleanField', 'EmailField',
+ 'IPAddressField', 'URLField', 'SlugField',
+)
+
+class DojoFieldMixin(object):
+ """
+ A general mixin for all custom django/dojo form fields.
+ It passes the field attributes in 'passed_attrs' to the form widget, so
+ they can be used there. The widget itself then evaluates which of these
+ fiels will be used.
+ """
+ passed_attrs = [ # forwarded field->widget attributes
+ 'required',
+ 'help_text',
+ 'min_value',
+ 'max_value',
+ 'max_length',
+ 'max_digits',
+ 'decimal_places',
+ 'js_regex', # special key for some dojo widgets
+ ]
+
+ def widget_attrs(self, widget):
+ """Called, when the field is instanitating the widget. Here we collect
+ all field attributes and pass it to the attributes of the widgets using
+ the 'extra_field_attrs' key. These additional attributes will be
+ evaluated by the widget and deleted within the 'DojoWidgetMixin'.
+ """
+ ret = {'extra_field_attrs': {}}
+ for field_attr in self.passed_attrs:
+ field_val = getattr(self, field_attr, None)
+ #print field_attr, widget, field_val
+ if field_val is not None:
+ ret['extra_field_attrs'][field_attr] = field_val
+ return ret
+
+###############################################
+# IMPLEMENTATION OF ALL EXISTING DJANGO FIELDS
+###############################################
+
+class CharField(DojoFieldMixin, fields.CharField):
+ widget = widgets.ValidationTextInput
+
+class ChoiceField(DojoFieldMixin, fields.ChoiceField):
+ widget = widgets.Select
+
+class TypedChoiceField(DojoFieldMixin, fields.TypedChoiceField):
+ widget = widgets.Select
+
+class IntegerField(DojoFieldMixin, fields.IntegerField):
+ widget = widgets.NumberTextInput
+ decimal_places = 0
+
+class BooleanField(DojoFieldMixin, fields.BooleanField):
+ widget = widgets.CheckboxInput
+
+class FileField(DojoFieldMixin, fields.FileField):
+ widget = widgets.FileInput
+
+class ImageField(DojoFieldMixin, fields.ImageField):
+ widget = widgets.FileInput
+
+DEFAULT_DATE_INPUT_FORMATS = tuple(list(fields.DEFAULT_DATE_INPUT_FORMATS) + [
+ '%Y-%m-%dT%H:%M', '%Y-%m-%dT%H:%M:%S'
+])
+class DateField(DojoFieldMixin, fields.DateField):
+ widget = widgets.DateInput
+
+ def __init__(self, input_formats=None, min_value=None, max_value=None, *args, **kwargs):
+ kwargs['input_formats'] = input_formats or DEFAULT_DATE_INPUT_FORMATS
+ self.max_value = max_value
+ self.min_value = min_value
+ super(DateField, self).__init__(*args, **kwargs)
+
+DEFAULT_TIME_INPUT_FORMATS = tuple(list(fields.DEFAULT_TIME_INPUT_FORMATS) + [
+ '%Y-%m-%dT%H:%M', '%Y-%m-%dT%H:%M:%S', 'T%H:%M:%S', 'T%H:%M'
+])
+class TimeField(DojoFieldMixin, fields.TimeField):
+ widget = widgets.TimeInput
+
+ def __init__(self, input_formats=None, min_value=None, max_value=None, *args, **kwargs):
+ kwargs['input_formats'] = input_formats or DEFAULT_TIME_INPUT_FORMATS
+ self.max_value = max_value
+ self.min_value = min_value
+ super(TimeField, self).__init__(*args, **kwargs)
+
+class DateTimeField(CharField):
+ widget = widgets.DateTimeInput
+
+ def __init__(self, min_value=None, max_value=None, *args, **kwargs):
+ self.max_value = max_value
+ self.min_value = min_value
+ super(DateTimeField, self).__init__(*args, **kwargs)
+
+SplitDateTimeField = DateTimeField # datetime input is always splitted
+
+class RegexField(DojoFieldMixin, fields.RegexField):
+ widget = widgets.ValidationTextInput
+ js_regex = None # we additionally have to define a custom javascript regexp, because the python one is not compatible to javascript
+
+ def __init__(self, js_regex=None, *args, **kwargs):
+ self.js_regex = js_regex
+ super(RegexField, self).__init__(*args, **kwargs)
+
+class DecimalField(DojoFieldMixin, fields.DecimalField):
+ widget = widgets.NumberTextInput
+
+class FloatField(DojoFieldMixin, fields.FloatField):
+ widget = widgets.ValidationTextInput
+
+class FilePathField(DojoFieldMixin, fields.FilePathField):
+ widget = widgets.Select
+
+class MultipleChoiceField(DojoFieldMixin, fields.MultipleChoiceField):
+ widget = widgets.SelectMultiple
+
+class NullBooleanField(DojoFieldMixin, fields.NullBooleanField):
+ widget = widgets.NullBooleanSelect
+
+class EmailField(DojoFieldMixin, fields.EmailField):
+ widget = widgets.EmailTextInput
+
+class IPAddressField(DojoFieldMixin, fields.IPAddressField):
+ widget = widgets.IPAddressTextInput
+
+class URLField(DojoFieldMixin, fields.URLField):
+ widget = widgets.URLTextInput
+
+class SlugField(DojoFieldMixin, fields.SlugField):
+ widget = widgets.ValidationTextInput
+ js_regex = '^[-\w]+$' # we cannot extract the original regex input from the python regex
70 apps/dojango/forms/formsets.py
View
@@ -0,0 +1,70 @@
+from django.forms.formsets import *
+from django.utils.translation import ugettext as _
+from django.forms.formsets import TOTAL_FORM_COUNT
+from django.forms.formsets import INITIAL_FORM_COUNT
+from django.forms.formsets import DELETION_FIELD_NAME
+from django.forms.formsets import formset_factory as django_formset_factory
+from django.forms.forms import Form
+
+from fields import IntegerField, BooleanField
+from widgets import Media, HiddenInput
+
+from django.forms.formsets import BaseFormSet
+
+__all__ = ('BaseFormSet', 'all_valid')
+
+class ManagementForm(Form):
+ """
+ Changed ManagementForm. It is using the dojango form fields.
+ """
+ def __init__(self, *args, **kwargs):
+ self.base_fields[TOTAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
+ self.base_fields[INITIAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
+ Form.__init__(self, *args, **kwargs)
+
+class BaseFormSet(BaseFormSet):
+ """
+ Overwritten BaseFormSet. Basically using the form extension of dojango.
+ """
+ def _dojango_management_form(self):
+ """Attaching our own ManagementForm"""
+ if self.data or self.files:
+ form = ManagementForm(self.data, auto_id=self.auto_id, prefix=self.prefix)
+ if not form.is_valid():
+ raise ValidationError('ManagementForm data is missing or has been tampered with')
+ else:
+ is_dojo_1_0 = getattr(self, "_total_form_count", False)
+ # this is for django versions before 1.1
+ initial = {
+ TOTAL_FORM_COUNT: is_dojo_1_0 and self._total_form_count or self.total_form_count(),
+ INITIAL_FORM_COUNT: is_dojo_1_0 and self._initial_form_count or self.initial_form_count()
+ }
+ form = ManagementForm(auto_id=self.auto_id, prefix=self.prefix, initial=initial)
+ return form
+ dojango_management_form = property(_dojango_management_form)
+
+ def __getattribute__(self, anatt):
+ """This is the superhack for overwriting the management_form
+ property of the super class using a newly defined ManagementForm.
+ In Django this property should've be defined lazy:
+ management_form = property(lambda self: self._management_form())
+ """
+ if anatt == 'management_form':
+ anatt = "dojango_management_form"
+ return super(BaseFormSet, self).__getattribute__(anatt)
+
+ def add_fields(self, form, index):
+ """Using the dojango form fields instead of the django ones"""
+ if self.can_order:
+ # Only pre-fill the ordering field for initial forms.
+ if index < self._initial_form_count:
+ form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_(u'Order'), initial=index+1, required=False)
+ else:
+ form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_(u'Order'), required=False)
+ if self.can_delete:
+ form.fields[DELETION_FIELD_NAME] = BooleanField(label=_(u'Delete'), required=False)
+
+def formset_factory(*args, **kwargs):
+ """Formset factory function that uses the dojango BaseFormSet"""
+ kwargs["formset"] = BaseFormSet
+ return django_formset_factory(*args, **kwargs)
203 apps/dojango/forms/models.py
View
@@ -0,0 +1,203 @@
+from django.forms import *
+from django.forms.models import BaseModelFormSet
+from django.forms.models import BaseInlineFormSet
+from django.forms.models import ModelChoiceIterator
+from django.forms.models import InlineForeignKeyHiddenInput, InlineForeignKeyField
+
+from django.utils.text import capfirst
+
+from formsets import BaseFormSet
+
+from django.db.models import fields
+
+from dojango.forms.fields import *
+from dojango.forms.widgets import DojoWidgetMixin, Textarea, Select, SelectMultiple, HiddenInput
+
+__all__ = (
+ 'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
+ 'save_instance', 'form_for_fields', 'ModelChoiceField',
+ 'ModelMultipleChoiceField',
+)
+
+class ModelChoiceField(DojoFieldMixin, models.ModelChoiceField):
+ """
+ Overwritten 'ModelChoiceField' using the 'DojoFieldMixin' functionality.
+ """
+ widget = Select
+
+class ModelMultipleChoiceField(DojoFieldMixin, models.ModelMultipleChoiceField):
+ """
+ Overwritten 'ModelMultipleChoiceField' using the 'DojoFieldMixin' functonality.
+ """
+ widget = SelectMultiple
+
+# Fields #####################################################################
+
+class InlineForeignKeyHiddenInput(DojoWidgetMixin, InlineForeignKeyHiddenInput):
+ """
+ Overwritten InlineForeignKeyHiddenInput to use the dojango widget mixin
+ """
+ dojo_type = 'dijit.form.TextBox' # otherwise dijit.form.Form can't get its values
+
+class InlineForeignKeyField(DojoFieldMixin, InlineForeignKeyField, Field):
+ """
+ Overwritten InlineForeignKeyField to use the dojango field mixin and passing
+ the dojango InlineForeignKeyHiddenInput as widget.
+ """
+ def __init__(self, parent_instance, *args, **kwargs):
+ self.parent_instance = parent_instance
+ self.pk_field = kwargs.pop("pk_field", False)
+ if self.parent_instance is not None:
+ kwargs["initial"] = self.parent_instance.pk
+ kwargs["required"] = False
+ kwargs["widget"] = InlineForeignKeyHiddenInput
+ # don't call the the superclass of this one. Use the superclass of the
+ # normal django InlineForeignKeyField
+ Field.__init__(self, *args, **kwargs)
+
+# our customized model field => form field map
+# here it is defined which form field is used by which model field, when creating a ModelForm
+MODEL_TO_FORM_FIELD_MAP = (
+ # (model_field, form_field, [optional widget])
+ # the order of these fields is very important for inherited model fields
+ # e.g. the CharField must be checked at last, because several other
+ # fields are a subclass of it.
+ (fields.CommaSeparatedIntegerField, CharField),
+ (fields.DateTimeField, DateTimeField), # must be in front of the DateField
+ (fields.DateField, DateField),
+ (fields.DecimalField, DecimalField),
+ (fields.EmailField, EmailField),
+ (fields.FilePathField, FilePathField),
+ (fields.FloatField, FloatField),
+ (fields.related.ForeignKey, ModelChoiceField),
+ (fields.files.ImageField, ImageField),
+ (fields.files.FileField, FileField),
+ (fields.IPAddressField, IPAddressField),
+ (fields.related.ManyToManyField, ModelMultipleChoiceField),
+ (fields.NullBooleanField, CharField),
+ (fields.BooleanField, BooleanField),
+ (fields.PositiveSmallIntegerField, IntegerField),
+ (fields.PositiveIntegerField, IntegerField),
+ (fields.SlugField, SlugField),
+ (fields.SmallIntegerField, IntegerField),
+ (fields.IntegerField, IntegerField),
+ (fields.TimeField, TimeField),
+ (fields.URLField, URLField),
+ (fields.XMLField, CharField, Textarea),
+ (fields.TextField, CharField, Textarea),
+ (fields.CharField, CharField),
+)
+
+def formfield_function(field):
+ """
+ Custom formfield function, so we can inject our own form fields. The
+ mapping of model fields to form fields is defined in 'MODEL_TO_FORM_FIELD_MAP'.
+ It uses the default django mapping as fallback, if there is no match in our
+ custom map.
+
+ field -- a model field
+ """
+ for field_map in MODEL_TO_FORM_FIELD_MAP:
+ if isinstance(field, field_map[0]):
+ used_widget = None
+ if field.choices:
+ # the normal django field forms.TypedChoiceField is wired hard
+ # within the original db/models/fields.py.
+ # If we use our custom Select widget, we also have to pass in
+ # some additional validation field attributes.
+ used_widget = Select(attrs={
+ 'extra_field_attrs':{
+ 'required':not field.blank,
+ 'help_text':field.help_text,
+ }
+ })
+ elif len(field_map) == 3:
+ widget=field_map[2]
+ if used_widget:
+ return field.formfield(form_class=field_map[1], widget=used_widget)
+ return field.formfield(form_class=field_map[1])
+ # return the default formfield, if there is no equivalent
+ return field.formfield()
+
+# ModelForms #################################################################
+
+def fields_for_model(*args, **kwargs):
+ """Changed fields_for_model function, where we use our own formfield_callback"""
+ kwargs["formfield_callback"] = formfield_function
+ return models.fields_for_model(*args, **kwargs)
+
+class ModelFormMetaclass(models.ModelFormMetaclass):
+ """
+ Overwritten 'ModelFormMetaClass'. We attach our own formfield generation
+ function.
+ """
+ def __new__(cls, name, bases, attrs):
+ # this is how we can replace standard django form fields with dojo ones
+ attrs["formfield_callback"] = formfield_function
+ return super(ModelFormMetaclass, cls).__new__(cls, name, bases, attrs)
+
+class ModelForm(models.ModelForm):
+ """
+ Overwritten 'ModelForm' using the metaclass defined above.
+ """
+ __metaclass__ = ModelFormMetaclass
+
+def modelform_factory(*args, **kwargs):
+ """Changed modelform_factory function, where we use our own formfield_callback"""
+ kwargs["formfield_callback"] = formfield_function
+ kwargs["formset"] = BaseModelForm
+ return models.modelform_factory(*args, **kwargs)
+
+# ModelFormSets ##############################################################
+
+class BaseModelFormSet(BaseModelFormSet, BaseFormSet):
+
+ def add_fields(self, form, index):
+ """Overwritten BaseModelFormSet using the dojango BaseFormSet and
+ the ModelChoiceField.
+ NOTE: This method was copied from django 1.1"""
+ from django.db.models import AutoField, OneToOneField, ForeignKey
+ self._pk_field = pk = self.model._meta.pk
+ def pk_is_not_editable(pk):
+ return ((not pk.editable) or (pk.auto_created or isinstance(pk, AutoField))
+ or (pk.rel and pk.rel.parent_link and pk_is_not_editable(pk.rel.to._meta.pk)))
+ if pk_is_not_editable(pk) or pk.name not in form.fields:
+ try:
+ pk_value = self.get_queryset()[index].pk
+ except IndexError:
+ pk_value = None
+ if isinstance(pk, OneToOneField) or isinstance(pk, ForeignKey):
+ qs = pk.rel.to._default_manager.get_query_set()
+ else:
+ qs = self.model._default_manager.get_query_set()
+ form.fields[self._pk_field.name] = ModelChoiceField(qs, initial=pk_value, required=False, widget=HiddenInput)
+ BaseFormSet.add_fields(self, form, index)
+
+def modelformset_factory(*args, **kwargs):
+ """Changed modelformset_factory function, where we use our own formfield_callback"""
+ kwargs["formfield_callback"] = formfield_function
+ kwargs["formset"] = BaseModelFormSet
+ return models.modelformset_factory(*args, **kwargs)
+
+# InlineFormSets #############################################################
+
+class BaseInlineFormSet(BaseInlineFormSet, BaseModelFormSet):
+ """Overwritten BaseInlineFormSet using the dojango InlineForeignKeyFields.
+ NOTE: This method was copied from django 1.1"""
+ def add_fields(self, form, index):
+ super(BaseInlineFormSet, self).add_fields(form, index)
+ if self._pk_field == self.fk:
+ form.fields[self._pk_field.name] = InlineForeignKeyField(self.instance, pk_field=True)
+ else:
+ kwargs = {
+ 'label': getattr(form.fields.get(self.fk.name), 'label', capfirst(self.fk.verbose_name))
+ }
+ if self.fk.rel.field_name != self.fk.rel.to._meta.pk.name:
+ kwargs['to_field'] = self.fk.rel.field_name
+ form.fields[self.fk.name] = InlineForeignKeyField(self.instance, **kwargs)
+
+def inlineformset_factory(*args, **kwargs):
+ """Changed inlineformset_factory function, where we use our own formfield_callback"""
+ kwargs["formfield_callback"] = formfield_function
+ kwargs["formset"] = BaseInlineFormSet
+ return models.inlineformset_factory(*args, **kwargs)
464 apps/dojango/forms/widgets.py
View
@@ -0,0 +1,464 @@
+import datetime
+
+from django.forms import *
+from django.utils.encoding import StrAndUnicode, force_unicode
+from django.utils.html import conditional_escape
+from django.utils.safestring import mark_safe
+from django.forms.util import flatatt
+from django.utils import datetime_safe
+
+from dojango.util import json_encode
+from dojango.util.config import Config
+
+from dojango.util import dojo_collector
+
+__all__ = (
+ 'Media', 'MediaDefiningClass', # original django classes
+ 'DojoWidgetMixin', 'Input', 'Widget', 'TextInput', 'PasswordInput',
+ 'HiddenInput', 'MultipleHiddenInput', 'FileInput', 'Textarea',
+ 'DateInput', 'DateTimeInput', 'TimeInput', 'CheckboxInput', 'Select',
+ 'NullBooleanSelect', 'SelectMultiple', 'RadioInput', 'RadioFieldRenderer',
+ 'RadioSelect', 'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
+ 'SplitHiddenDateTimeWidget', 'SimpleTextarea', 'EditorInput', 'HorizontalSliderInput',
+ 'VerticalSliderInput', 'ValidationTextInput', 'ValidationPasswordInput',
+ 'EmailTextInput', 'IPAddressTextInput', 'URLTextInput', 'NumberTextInput',
+ 'RangeBoundTextInput', 'NumberSpinnerInput', 'RatingInput', 'DateInputAnim',
+ 'DropDownSelect', 'CheckedMultiSelect', 'ComboBox',
+)
+
+dojo_config = Config() # initialize the configuration
+
+class DojoWidgetMixin:
+ """A helper mixin, that is used by every custom dojo widget.
+ Some dojo widgets can utilize the validation information of a field and here
+ we mixin those attributes into the widget. Field attributes that are listed
+ in the 'valid_extra_attrs' will be mixed into the attributes of a widget.
+
+ The 'default_field_attr_map' property contains the default mapping of field
+ attributes to dojo widget attributes.
+
+ This mixin also takes care passing the required dojo modules to the collector.
+ 'dojo_type' defines the used dojo module type of this widget and adds this
+ module to the collector, if no 'alt_require' property is defined. When
+ 'alt_require' is set, this module will be passed to the collector. By using
+ 'extra_dojo_require' it is possible to pass additional dojo modules to the
+ collector.
+ """
+ dojo_type = None # this is the dojoType definition of the widget. also used for generating the dojo.require call
+ alt_require = None # alternative dojo.require call (not using the dojo_type)
+ extra_dojo_require = [] # these dojo modules also needs to be loaded for this widget
+
+ default_field_attr_map = { # the default map for mapping field attributes to dojo attributes
+ 'required':'required',
+ 'help_text':'promptMessage',
+ 'min_value':'constraints.min',
+ 'max_value':'constraints.max',
+ 'max_length':'maxLength',
+ #'max_digits':'maxDigits',
+ 'decimal_places':'constraints.places',
+ 'js_regex':'regExp',
+ 'multiple':'multiple',
+ }
+ field_attr_map = {} # used for overwriting the default attr-map
+ valid_extra_attrs = [] # these field_attributes are valid for the current widget
+
+ def _mixin_attr(self, attrs, key, value):
+ """Mixes in the passed key/value into the passed attrs and returns that
+ extended attrs dictionary.
+
+ A 'key', that is separated by a dot, e.g. 'constraints.min', will be
+ added as:
+
+ {'constraints':{'min':value}}
+ """
+ dojo_field_attr = key.split(".")
+ inner_dict = attrs
+ len_fields = len(dojo_field_attr)
+ count = 0
+ for i in dojo_field_attr:
+ count = count+1
+ if count == len_fields and inner_dict.get(i, None) is None:
+ if isinstance(value, datetime.datetime):
+ if isinstance(self, TimeInput):
+ value = value.strftime('T%H:%M:%S')
+ if isinstance(self, DateInput):
+ value = value.strftime('%Y-%m-%d')
+ value = str(value).replace(' ', 'T') # see dojo.date.stamp
+ if isinstance(value, datetime.date):
+ value = str(value)
+ if isinstance(value, datetime.time):
+ value = "T" + str(value) # see dojo.date.stamp
+ inner_dict[i] = value
+ elif not inner_dict.has_key(i):
+ inner_dict[i] = {}
+ inner_dict = inner_dict[i]
+ return attrs
+
+ def build_attrs(self, extra_attrs=None, **kwargs):
+ """Overwritten helper function for building an attribute dictionary.
+ This helper also takes care passing the used dojo modules to the
+ collector. Furthermore it mixes in the used field attributes into the
+ attributes of this widget.
+ """
+ # gathering all widget attributes
+ attrs = dict(self.attrs, **kwargs)
+ field_attr = self.default_field_attr_map.copy() # use a copy of that object. otherwise changed field_attr_map would overwrite the default-map for all widgets!
+ field_attr.update(self.field_attr_map) # the field-attribute-mapping can be customzied
+ if extra_attrs:
+ attrs.update(extra_attrs)
+ # assigning dojoType to our widget
+ dojo_type = getattr(self, "dojo_type", False)
+ if dojo_type:
+ attrs["dojoType"] = dojo_type # add the dojoType attribute
+
+ # fill the global collector object
+ if getattr(self, "alt_require", False):
+ dojo_collector.add_module(self.alt_require)
+ elif dojo_type:
+ dojo_collector.add_module(self.dojo_type)
+ extra_requires = getattr(self, "extra_dojo_require", [])
+ for i in extra_requires:
+ dojo_collector.add_module(i)
+
+ # mixin those additional field attrs, that are valid for this widget
+ extra_field_attrs = attrs.get("extra_field_attrs", False)
+ if extra_field_attrs:
+ for i in self.valid_extra_attrs:
+ field_val = extra_field_attrs.get(i, None)
+ new_attr_name = field_attr.get(i, None)
+ if field_val is not None and new_attr_name is not None:
+ attrs = self._mixin_attr(attrs, new_attr_name, field_val)
+ del attrs["extra_field_attrs"]
+
+ # now encode several attributes, e.g. False = false, True = true
+ for i in attrs:
+ if isinstance(attrs[i], bool):
+ attrs[i] = json_encode(attrs[i])
+ return attrs
+
+#############################################
+# ALL OVERWRITTEN DEFAULT DJANGO WIDGETS
+#############################################
+
+class Widget(DojoWidgetMixin, widgets.Widget):
+ dojo_type = 'dijit._Widget'
+
+class Input(DojoWidgetMixin, widgets.Input):
+ pass
+
+class TextInput(DojoWidgetMixin, widgets.TextInput):
+ dojo_type = 'dijit.form.TextBox'
+ valid_extra_attrs = [
+ 'max_length',
+ ]
+
+class PasswordInput(DojoWidgetMixin, widgets.PasswordInput):
+ dojo_type = 'dijit.form.TextBox'
+ valid_extra_attrs = [
+ 'max_length',
+ ]
+
+class HiddenInput(DojoWidgetMixin, widgets.HiddenInput):
+ dojo_type = 'dijit.form.TextBox' # otherwise dijit.form.Form can't get its values
+
+class MultipleHiddenInput(DojoWidgetMixin, widgets.MultipleHiddenInput):
+ dojo_type = 'dijit.form.TextBox' # otherwise dijit.form.Form can't get its values
+
+class FileInput(DojoWidgetMixin, widgets.FileInput):
+ dojo_type = 'dojox.form.FileInput'
+ class Media:
+ css = {
+ 'all': ('%(base_url)s/dojox/form/resources/FileInput.css' % {
+ 'base_url':dojo_config.dojo_base_url
+ },)
+ }
+
+class Textarea(DojoWidgetMixin, widgets.Textarea):
+ """Auto resizing textarea"""
+ dojo_type = 'dijit.form.Textarea'
+ valid_extra_attrs = [
+ 'max_length'
+ ]
+
+class DateInput(TextInput):
+ """Copy of the implementation in Django 1.1. Before this widget did not exists."""
+ dojo_type = 'dijit.form.DateTextBox'
+ valid_extra_attrs = [
+ 'required',
+ 'help_text',
+ 'min_value',
+ 'max_value',
+ ]
+ format = '%Y-%m-%d' # '2006-10-25'
+ def __init__(self, attrs=None, format=None):
+ super(DateInput, self).__init__(attrs)
+ if format:
+ self.format = format
+
+ def render(self, name, value, attrs=None):
+ if value is None:
+ value = ''
+ elif hasattr(value, 'strftime'):
+ value = datetime_safe.new_date(value)
+ value = value.strftime(self.format)
+ return super(DateInput, self).render(name, value, attrs)
+
+class TimeInput(TextInput):
+ """Copy of the implementation in Django 1.1. Before this widget did not exists."""
+ dojo_type = 'dijit.form.TimeTextBox'
+ valid_extra_attrs = [
+ 'required',
+ 'help_text',
+ 'min_value',
+ 'max_value',
+ ]
+ format = "T%H:%M:%S" # special for dojo: 'T12:12:33'
+ def __init__(self, attrs=None, format=None):
+ super(TimeInput, self).__init__(attrs)
+ if format:
+ self.format = format
+
+ def render(self, name, value, attrs=None):
+ if value is None:
+ value = ''
+ elif hasattr(value, 'strftime'):
+ value = value.strftime(self.format)
+ return super(TimeInput, self).render(name, value, attrs)
+
+class CheckboxInput(DojoWidgetMixin, widgets.CheckboxInput):
+ dojo_type = 'dijit.form.CheckBox'
+
+class Select(DojoWidgetMixin, widgets.Select):
+ dojo_type = 'dijit.form.FilteringSelect'
+ valid_extra_attrs = [
+ 'required',
+ 'help_text',
+ ]
+
+class NullBooleanSelect(DojoWidgetMixin, widgets.NullBooleanSelect):
+ dojo_type = 'dijit.form.FilteringSelect'
+
+class SelectMultiple(DojoWidgetMixin, widgets.SelectMultiple):
+ dojo_type = 'dijit.form.MultiSelect'
+
+RadioInput = widgets.RadioInput
+RadioFieldRenderer = widgets.RadioFieldRenderer
+
+class RadioSelect(DojoWidgetMixin, widgets.RadioSelect):
+ dojo_type = 'dijit.form.RadioButton'
+
+ def __init__(self, attrs=None):
+ if dojo_config.version < '1.3':
+ self.alt_require = 'dijit.form.CheckBox'
+ super(RadioSelect, self).__init__(attrs)
+
+class CheckboxSelectMultiple(DojoWidgetMixin, widgets.CheckboxSelectMultiple):
+ dojo_type = 'dijit.form.CheckBox'
+
+class MultiWidget(DojoWidgetMixin, widgets.MultiWidget):
+ dojo_type = None
+
+class SplitDateTimeWidget(widgets.SplitDateTimeWidget):
+ "DateTimeInput is using two input fields."
+ date_format = DateInput.format
+ time_format = TimeInput.format
+
+ def __init__(self, attrs=None, date_format=None, time_format=None):
+ if date_format:
+ self.date_format = date_format
+ if time_format:
+ self.time_format = time_format
+ split_widgets = (DateInput(attrs=attrs, format=self.date_format),
+ TimeInput(attrs=attrs, format=self.time_format))
+ # Note that we're calling MultiWidget, not SplitDateTimeWidget, because
+ # we want to define widgets.
+ widgets.MultiWidget.__init__(self, split_widgets, attrs)
+
+class SplitHiddenDateTimeWidget(DojoWidgetMixin, widgets.SplitHiddenDateTimeWidget):
+ dojo_type = "dijit.form.TextBox"
+
+DateTimeInput = SplitDateTimeWidget
+
+#############################################
+# MORE ENHANCED DJANGO/DOJO WIDGETS
+#############################################
+
+class SimpleTextarea(Textarea):
+ """No autoexpanding textarea"""
+ dojo_type = "dijit.form.SimpleTextarea"
+
+class EditorInput(Textarea):
+ dojo_type = 'dijit.Editor'
+
+ def render(self, name, value, attrs=None):
+ if value is None: value = ''
+ final_attrs = self.build_attrs(attrs, name=name)
+ # dijit.Editor must be rendered in a div (see dijit/_editor/RichText.js)
+ return mark_safe(u'<div%s>%s</div>' % (flatatt(final_attrs),
+ force_unicode(value))) # we don't escape the value for the editor
+
+class HorizontalSliderInput(TextInput):
+ dojo_type = 'dijit.form.HorizontalSlider'
+ valid_extra_attrs = [
+ 'max_value',
+ 'min_value',
+ ]
+ field_attr_map = {
+ 'max_value': 'maximum',
+ 'min_value': 'minimum',
+ }
+
+ def __init__(self, attrs=None):
+ if dojo_config.version < '1.3':
+ self.alt_require = 'dijit.form.Slider'
+ super(HorizontalSliderInput, self).__init__(attrs)
+
+class VerticalSliderInput(HorizontalSliderInput):
+ dojo_type = 'dijit.form.VerticalSlider'
+
+class ValidationTextInput(TextInput):
+ dojo_type = 'dijit.form.ValidationTextBox'
+ valid_extra_attrs = [
+ 'required',
+ 'help_text',
+ 'js_regex',
+ 'max_length',
+ ]
+ js_regex_func = None
+
+ def render(self, name, value, attrs=None):
+ if self.js_regex_func:
+ attrs = self.build_attrs(attrs, regExpGen=self.js_regex_func)
+ return super(ValidationTextInput, self).render(name, value, attrs)
+
+class ValidationPasswordInput(PasswordInput):
+ dojo_type = 'dijit.form.ValidationTextBox'
+ valid_extra_attrs = [
+ 'required',
+ 'help_text',
+ 'js_regex',
+ 'max_length',
+ ]
+
+class EmailTextInput(ValidationTextInput):
+ extra_dojo_require = [
+ 'dojox.validate.regexp'
+ ]
+ js_regex_func = "dojox.validate.regexp.emailAddress"
+
+ def __init__(self, attrs=None):
+ if dojo_config.version < '1.3':
+ self.js_regex_func = 'dojox.regexp.emailAddress'
+ super(EmailTextInput, self).__init__(attrs)
+
+class IPAddressTextInput(ValidationTextInput):
+ extra_dojo_require = [
+ 'dojox.validate.regexp'
+ ]
+ js_regex_func = "dojox.validate.regexp.ipAddress"
+
+ def __init__(self, attrs=None):
+ if dojo_config.version < '1.3':
+ self.js_regex_func = 'dojox.regexp.ipAddress'
+ super(IPAddressTextInput, self).__init__(attrs)
+
+class URLTextInput(ValidationTextInput):
+ extra_dojo_require = [
+ 'dojox.validate.regexp'
+ ]
+ js_regex_func = "dojox.validate.regexp.url"
+
+ def __init__(self, attrs=None):
+ if dojo_config.version < '1.3':
+ self.js_regex_func = 'dojox.regexp.url'
+ super(URLTextInput, self).__init__(attrs)
+
+class NumberTextInput(TextInput):
+ dojo_type = 'dijit.form.NumberTextBox'
+ valid_extra_attrs = [
+ 'min_value',
+ 'max_value',
+ 'required',
+ 'help_text',
+ 'decimal_places',
+ ]
+
+class RangeBoundTextInput(NumberTextInput):
+ dojo_type = 'dijit.form.RangeBoundTextBox'
+
+class NumberSpinnerInput(NumberTextInput):
+ dojo_type = 'dijit.form.NumberSpinner'
+
+class RatingInput(TextInput):
+ dojo_type = 'dojox.form.Rating'
+ valid_extra_attrs = [
+ 'max_value',
+ ]
+ field_attr_map = {
+ 'max_value': 'numStars',
+ }
+
+ class Media:
+ css = {
+ 'all': ('%(base_url)s/dojox/form/resources/Rating.css' % {
+ 'base_url':dojo_config.dojo_base_url
+ },)
+ }
+
+class DateInputAnim(DateInput):
+ dojo_type = 'dojox.form.DateTextBox'
+ class Media:
+ css = {
+ 'all': ('%(base_url)s/dojox/widget/Calendar/Calendar.css' % {
+ 'base_url':dojo_config.dojo_base_url
+ },)
+ }
+
+class DropDownSelect(Select):
+ dojo_type = 'dojox.form.DropDownSelect'
+ valid_extra_attrs = []
+ class Media:
+ css = {
+ 'all': ('%(base_url)s/dojox/form/resources/DropDownSelect.css' % {
+ 'base_url':dojo_config.dojo_base_url
+ },)
+ }
+
+class CheckedMultiSelect(SelectMultiple):
+ dojo_type = 'dojox.form.CheckedMultiSelect'
+ valid_extra_attrs = []
+ # TODO: fix attribute multiple=multiple
+ # seems there is a dependency in dojox.form.CheckedMultiSelect for dijit.form.MultiSelect,
+ # but CheckedMultiSelect is not extending that
+
+ class Media:
+ css = {
+ 'all': ('%(base_url)s/dojox/form/resources/CheckedMultiSelect.css' % {
+ 'base_url':dojo_config.dojo_base_url
+ },)
+ }
+
+class ComboBox(Select):
+ """Nearly the same as FilteringSelect, but ignoring the option value."""
+ dojo_type = 'dijit.form.ComboBox'
+
+# THE RANGE SLIDER NEEDS A DIFFERENT REPRESENTATION IN THE FRONTEND
+# SOMETHING LIKE:
+# <div dojoType="dojox.form.RangeSlider"><input value="5"/><input value="10"/></div>
+'''class HorizontalRangeSlider(HorizontalSliderInput):
+ """This just can be used with a comma-separated-value like: 20,40"""
+ dojo_type = 'dojox.form.HorizontalRangeSlider'
+ alt_require = 'dojox.form.RangeSlider'
+
+ class Media:
+ css = {
+ 'all': ('%(base_url)s/dojox/form/resources/RangeSlider.css' % {
+ 'base_url':dojo_config.dojo_base_url
+ },)
+ }
+'''
+# TODO: implement
+# dijit.form.ComboBox (the more extended case, that is using a store! ForeignKeyField ...)
+# dojox.form.RangeSlider
+# dojox.form.MultiComboBox
+# dojox.form.FileUploader
0  apps/dojango/management/__init__.py
View
No changes.
BIN  apps/dojango/management/__init__.pyc
View
Binary file not shown
0  apps/dojango/management/commands/__init__.py
View
No changes.
317 apps/dojango/management/commands/dojobuild.py
View
@@ -0,0 +1,317 @@
+from optparse import make_option
+
+import os
+import re
+import shutil
+from dojango.conf import settings
+
+try:
+ from django.core.management.base import BaseCommand, CommandError
+except ImportError:
+ # Fake BaseCommand out so imports on django 0.96 don't fail.
+ BaseCommand = object
+ class CommandError(Exception):
+ pass
+
+class Command(BaseCommand):
+ '''This command is used to create your own dojo build. To start a build, you just
+ have to type:
+
+ ./manage.py dojobuild
+
+ in your django project path. With this call, the default build profile "dojango" is used
+ and dojango.profile.js will act as its dojo build configuration. You can also add the
+ option --build_version=dev1.1.1 (for example) to mark the build with it.
+ If you want to call a specific build profile from DOJO_BUILD_PROFILES, you just have to
+ append the profile name to this commandline call:
+
+ ./manage.py dojobuild profilename
+
+ '''
+
+ option_list = BaseCommand.option_list + (
+ make_option('--build_version', dest='build_version',
+ help='Set the version of the build release (e.g. dojango_1.1.1).'),
+ make_option('--minify', dest='minify', action="store_true", default=False,
+ help='Does a dojo mini build (mainly removing unneeded files (tests/templates/...)'),
+ make_option('--minify_extreme', dest='minify_extreme', action="store_true", default=False,
+ help='Does a dojo extreme-mini build (keeps only what is defined in build profile and all media files)'),
+ make_option('--prepare_zipserve', dest='prepare_zipserve', action="store_true", default=False,
+ help='Zips everything you have built, so it can be deployed to Google AppEngine'),
+ )
+ help = "Builds a dojo release."
+ args = '[dojo build profile name]'
+ dojo_base_dir = None
+ dojo_release_dir = None
+ skip_files = None
+
+ def handle(self, *args, **options):
+ if len(args)==0:
+ # with no param, we use the default profile, that is defined in the settings
+ profile_name = settings.DOJO_BUILD_PROFILE
+ else:
+ profile_name = args[0]
+ profile = self._get_profile(profile_name)
+ used_src_version = profile['used_src_version'] % {'DOJO_BUILD_VERSION': settings.DOJO_BUILD_VERSION} # no dependencies to project's settings.py file!
+ profile_file = os.path.basename(profile['profile_file'] % {'BASE_MEDIA_ROOT':settings.BASE_MEDIA_ROOT})
+ # used by minify_extreme!
+ self.skip_files = profile.get("minify_extreme_skip_files", ())
+ self.dojo_base_dir = "%(dojo_root)s/%(version)s" % \
+ {'dojo_root':settings.BASE_DOJO_ROOT,
+ 'version':used_src_version}
+ # does the defined dojo-directory exist?
+ util_base_dir = "%(dojo_base_dir)s/util" % {'dojo_base_dir':self.dojo_base_dir}
+ if not os.path.exists(util_base_dir):
+ raise CommandError('Put the the dojo source files (version \'%(version)s\') in the folder \'%(folder)s/%(version)s\'' % \
+ {'version':used_src_version,
+ 'folder':settings.BASE_DOJO_ROOT})
+ # check, if java is installed
+ stdin, stdout, stderr = os.popen3(settings.DOJO_BUILD_JAVA_EXEC)
+ if stderr.read():
+ raise CommandError('Please install java. You need it for building dojo.')
+ dest_profile_file = util_base_dir + "/buildscripts/profiles/%(profile_file)s" % \
+ {'profile_file':profile_file}
+ # copy the profile to the
+ shutil.copyfile(profile['profile_file'] % {'BASE_MEDIA_ROOT':settings.BASE_MEDIA_ROOT}, dest_profile_file)
+ buildscript_dir = os.path.abspath('%s/buildscripts' % util_base_dir)
+ if settings.DOJO_BUILD_USED_VERSION < '1.2.0':
+ executable = '%(java_exec)s -jar ../shrinksafe/custom_rhino.jar build.js' % \
+ {'java_exec':settings.DOJO_BUILD_JAVA_EXEC}
+ else:
+ # use the new build command line call!
+ if(os.path.sep == "\\"):
+ executable = 'build.bat'
+ else:
+ executable = './build.sh'
+ # force executable rights!
+ os.chmod(os.path.join(buildscript_dir, 'build.sh'), 0755)
+ # use the passed version for building
+ version = options.get('build_version', None)
+ if not version:
+ # if no option --build_version was passed, we use the default build version
+ version = profile['build_version'] % {'DOJO_BUILD_VERSION': settings.DOJO_BUILD_VERSION} # no dependencies to project's settings.py file!
+ # we add the version to our destination base path
+ self.dojo_release_dir = '%(base_path)s/%(version)s' % {
+ 'base_path':profile['base_root'] % {'BASE_MEDIA_ROOT':settings.BASE_MEDIA_ROOT},
+ 'version':version} # we don't want to have a dependancy to the project's settings file!
+ # the build command handling is so different between the versions!
+ # sometimes we need to add /, sometimes not :-(
+ # if settings.DOJO_BUILD_USED_VERSION < '1.2.0':
+ release_dir = os.path.abspath(os.path.join(self.dojo_release_dir, "../")) + os.path.sep
+ # setting up the build command
+ build_addons = ""
+ if settings.DOJO_BUILD_USED_VERSION >= '1.2.0':
+ # since version 1.2.0 there is an additional commandline option that does the mini build (solved within js!)
+ build_addons = "mini=true"
+ exe_command = 'cd %(buildscript_dir)s && %(executable)s version=%(version)s releaseName="%(version)s" releaseDir=%(release_dir)s %(options)s %(build_addons)s' % \
+ {'buildscript_dir':buildscript_dir,
+ 'executable':executable,
+ 'version':version,
+ 'release_dir':release_dir,
+ 'options':profile['options'],
+ 'build_addons':build_addons}
+ # print exe_command
+ minify = options['minify']
+ minify_extreme = options['minify_extreme']
+ prepare_zipserve = options['prepare_zipserve']
+ if settings.DOJO_BUILD_USED_VERSION < '1.2.0' and (minify or minify_extreme):
+ self._dojo_mini_before_build()
+ # do the build
+ os.system(exe_command)
+ if settings.DOJO_BUILD_USED_VERSION < '1.2.0':
+ if minify or minify_extreme:
+ self._dojo_mini_after_build()
+ if minify_extreme:
+ self._dojo_mini_extreme()
+ if prepare_zipserve:
+ self._dojo_prepare_zipserve()
+ os.remove(dest_profile_file) # remove the copied profile file
+
+ def _get_profile(self, name):
+ default_profile_settings = settings.DOJO_BUILD_PROFILES_DEFAULT
+ try:
+ profile = settings.DOJO_BUILD_PROFILES[name]
+ # mixing in the default settings for the build profiles!
+ default_profile_settings.update(profile)
+ return default_profile_settings
+ except KeyError:
+ raise CommandError('The profile \'%s\' does not exist in DOJO_BUILD_PROFILES' % name)
+
+ def _dojo_mini_before_build(self):
+ # FIXME: refs #6616 - could be able to set a global copyright file and null out build_release.txt
+ shutil.move("%s/util/buildscripts/copyright.txt" % self.dojo_base_dir, "%s/util/buildscripts/_copyright.txt" % self.dojo_base_dir)
+ if not os.path.exists("%s/util/buildscripts/copyright_mini.txt" % self.dojo_base_dir):
+ f = open("%s/util/buildscripts/copyright.txt" % self.dojo_base_dir, 'w')
+ f.write('''/*
+Copyright (c) 2004-2008, The Dojo Foundation All Rights Reserved.
+Available via Academic Free License >= 2.1 OR the modified BSD license.
+see: http://dojotoolkit.org/license for details
+*/''')
+ f.close()
+ else:
+ shutil.copyfile("%s/util/buildscripts/copyright_mini.txt" % self.dojo_base_dir, "%s/util/buildscripts/copyright.txt" % self.dojo_base_dir)
+ shutil.move("%s/util/buildscripts/build_notice.txt" % self.dojo_base_dir, "%s/util/buildscripts/_build_notice.txt" % self.dojo_base_dir)
+ # create an empty build-notice-file
+ f = open("%s/util/buildscripts/build_notice.txt" % self.dojo_base_dir, 'w')
+ f.close()
+
+ def _dojo_mini_after_build(self):
+ try:
+ '''Copied from the build_mini.sh shell script (thank you Pete Higgins :-))'''
+ if not os.path.exists(self.dojo_release_dir):
+ raise CommandError('The dojo build failed! Check messages above!')
+ else:
+ # remove dojox tests and demos - they all follow this convetion
+ self._remove_files('%s/dojox' % self.dojo_release_dir, ('^tests$', '^demos$'))
+ # removed dijit tests
+ dijit_tests = ("dijit/tests", "dijit/demos", "dijit/bench",
+ "dojo/tests", "util",
+ "dijit/themes/themeTesterImages")
+ self._remove_folders(dijit_tests)
+ # noir isn't worth including yet
+ noir_theme_path = ("%s/dijit/themes/noir" % self.dojo_release_dir,)
+ self._remove_folders(noir_theme_path)
+ # so the themes are there, lets assume that, piggyback on noir: FIXME later
+ self._remove_files('%s/dijit/themes' % self.dojo_release_dir, ('^.*\.html$',))
+ self._remove_files(self.dojo_release_dir, ('^.*\.uncompressed\.js$',))
+ # WARNING: templates have been inlined into the .js -- if you are using dynamic templates,
+ # or other build trickery, these lines might not work!
+ self._remove_files("dijit/templates", ("^\.html$",))
+ self._remove_files("dijit/form/templates", ("^\.html$",))
+ self._remove_files("dijit/layout/templates", ("^\.html$",))
+ # .. assume you didn't, and clean up all the README's (leaving LICENSE, mind you)
+ self._remove_files('%s/dojo/dojox' % self.dojo_release_dir, ('^README$',))
+ dojo_folders = ("dojo/_base",)
+ self._remove_folders(dojo_folders)
+ os.remove("%s/dojo/_base.js" % self.dojo_release_dir)
+ os.remove("%s/dojo/build.txt" % self.dojo_release_dir)
+ os.remove("%s/dojo/tests.js" % self.dojo_release_dir)
+ except Exception, e:
+ print e
+ # cleanup from above, refs #6616
+ shutil.move("%s/util/buildscripts/_copyright.txt" % self.dojo_base_dir, "%s/util/buildscripts/copyright.txt" % self.dojo_base_dir)
+ shutil.move("%s/util/buildscripts/_build_notice.txt" % self.dojo_base_dir, "%s/util/buildscripts/build_notice.txt" % self.dojo_base_dir)
+
+ def _remove_folders(self, folders):
+ for folder in folders:
+ if os.path.exists("%s/%s" % (self.dojo_release_dir, folder)):
+ shutil.rmtree("%s/%s" % (self.dojo_release_dir, folder))
+
+ def _remove_files(self, base_folder, regexp_list):
+ for root, dirs, files in os.walk(base_folder):
+ for file in files:
+ # remove all html-files
+ for regexp in regexp_list:
+ my_re = re.compile(regexp)
+ if my_re.match(file):
+ os.remove(os.path.join(root, file))
+ for dir in dirs:
+ for regexp in regexp_list:
+ my_re = re.compile(regexp)
+ if my_re.match(dir):
+ shutil.rmtree(os.path.join(root, dir))
+
+ SKIP_FILES = (
+ '(.*\.png)',
+ '(.*\.gif)',
+ '(.*\.jpg)',
+ '(.*\.svg)',
+ '(.*\.swf)',
+ '(.*\.fla)',
+ '(.*\.mov)',
+ '(.*\.smd)',
+ '(dojo/_firebug/firebug\..*)',
+ '(dojo/dojo\.(xd\.)?js)',
+ '(dojo/nls/.*)',
+ '(dojo/resources/dojo\.css)',
+ '(dojo/resources/blank\.html)',
+ '(dojo/resources/iframe_history\.html)',
+ '(dijit/themes/tundra/tundra\.css)',
+ '(dijit/themes/soria/soria\.css)',
+ '(dijit/themes/nihilo/nihilo\.css)',
+ '(dojox/dtl/contrib/.*)',
+ '(dojox/dtl/ext-dojo/.*)',
+ '(dojox/dtl/filter/.*)',
+ '(dojox/dtl/render/.*)',
+ '(dojox/dtl/tag/.*)',
+ '(dojox/dtl/utils/.*)',
+ '(dojox/io/proxy/xip_.*\.html)',
+ )
+ def _dojo_mini_extreme(self):
+ """
+ This method removes all js files and just leaves all layer dojo files and static files (like "png", "gif", "svg", "swf", ...)
+ """
+ # prepare the regexp of files not to be removed!
+ # mixin the profile specific skip files
+ skip_files = self.SKIP_FILES + self.skip_files
+ my_re = re.compile('^(.*/)?(%s)$' % "|".join(skip_files))
+ try:
+ '''Copied from the build_mini.sh shell script'''
+ if not os.path.exists(self.dojo_release_dir):
+ raise CommandError('The dojo build failed! Check messages above!')
+ else:
+ for root, dirs, files in os.walk(self.dojo_release_dir):
+ for file in files:
+ # remove all html-files
+ my_file = os.path.abspath(os.path.join(root, file))
+ if not my_re.match(my_file):
+ os.remove(my_file)
+ # now remove all empty directories
+ for root, dirs, files in os.walk(self.dojo_release_dir):
+ for dir in dirs:
+ try:
+ # just empty directories will be removed!
+ os.removedirs(os.path.join(root, dir))
+ except OSError:
+ pass
+ except Exception, e:
+ print e
+
+ DOJO_ZIP_SPECIAL = {'dojox': ['form', 'widget', 'grid']} # these modules will be zipped separately
+ def _dojo_prepare_zipserve(self):
+ """
+ Creates zip packages for each dojo module within the current release folder.
+ It splits the module dojox into several modules, so it fits the 1000 files limit of
+ Google AppEngine.
+ """
+ for folder in os.listdir(self.dojo_release_dir):
+ module_dir = '%s/%s' % (self.dojo_release_dir, folder)
+ if os.path.isdir(module_dir):
+ if folder in self.DOJO_ZIP_SPECIAL.keys():
+ for special_module in self.DOJO_ZIP_SPECIAL[folder]:
+ special_module_dir = os.path.join(module_dir, special_module)
+ create_zip(special_module_dir,
+ '%(base_module)s/%(special_module)s' % {
+ 'base_module': folder,
+ 'special_module': special_module
+ },
+ '%(module_dir)s.%(special_module)s.zip' % {
+ 'module_dir': module_dir,
+ 'special_module': special_module
+ }
+ )
+ # remove the whole special module
+ shutil.rmtree(special_module_dir)
+ # now add the
+ create_zip(module_dir, folder, module_dir + ".zip")
+ shutil.rmtree(module_dir)
+
+
+def zipfolder(path, relname, archive):
+ paths = os.listdir(path)
+ for p in paths:
+ p1 = os.path.join(path, p)
+ p2 = os.path.join(relname, p)
+ if os.path.isdir(p1):
+ zipfolder(p1, p2, archive)
+ else:
+ archive.write(p1, p2)
+
+def create_zip(path, relname, archname):
+ import zipfile
+ archive = zipfile.ZipFile(archname, "w", zipfile.ZIP_DEFLATED)
+ if os.path.isdir(path):
+ zipfolder(path, relname, archive)
+ else:
+ archive.write(path, relname)
+ archive.close()
105 apps/dojango/management/commands/dojoload.py
View
@@ -0,0 +1,105 @@
+import os
+import sys
+import urllib
+import zipfile
+
+from optparse import make_option
+from dojango.conf import settings
+
+try:
+ from django.core.management.base import BaseCommand, CommandError
+except ImportError:
+ # Fake BaseCommand out so imports on django 0.96 don't fail.
+ BaseCommand = object
+ class CommandError(Exception):
+ pass
+
+class Command(BaseCommand):
+ '''This command helps with downloading a dojo source release. To download
+ the currently defined 'settings.DOJANGO_DOJO_VERSION' just type:
+
+ ./manage.py dojoload
+
+ in your django project path. For downloading a specific version a version
+ string can be appended.
+
+ ./manage.py dojoload --version 1.2.3
+ '''
+
+ option_list = BaseCommand.option_list + (
+ make_option('--dojo_version', dest='dojo_version',
+ help='Download a defined version (e.g. 1.2.3) instead of the default (%s).' % settings.DOJO_VERSION),
+ )
+ help = "Downloads a dojo source release."
+ dl_url = "http://download.dojotoolkit.org/release-%(version)s/dojo-release-%(version)s-src.zip"
+ dl_to_path = settings.BASE_DOJO_ROOT + "/dojo-release-%(version)s-src.zip"
+ move_from_dir = settings.BASE_DOJO_ROOT + "/dojo-release-%(version)s-src"
+ move_to_dir = settings.BASE_DOJO_ROOT + "/%(version)s"
+ extract_to_dir = settings.BASE_DOJO_ROOT
+ total_kb = 0
+ downloaded_kb = 0
+
+
+ def handle(self, *args, **options):
+ version = settings.DOJO_VERSION
+ passed_version = options.get('dojo_version', None)
+ if passed_version:
+ version = passed_version
+ dl_url = self.dl_url % {'version': version}
+ dl_to_path = self.dl_to_path % {'version': version}
+ move_from_dir = self.move_from_dir % {'version': version}
+ move_to_dir = self.move_to_dir % {'version': version}
+ if os.path.exists(move_to_dir):
+ raise CommandError("You've already downloaded version %(version)s to %(move_to_dir)s" % {
+ 'version':version,
+ 'move_to_dir':move_to_dir,
+ })
+ else:
+ print "Downloading %s to %s" % (dl_url, dl_to_path)
+ self.download(dl_url, dl_to_path)
+ if self.total_kb == -1: # stupid bug in urllib (there is no IOError thrown, when a 404 occurs
+ os.remove(dl_to_path)
+ print ""
+ raise CommandError("There is no source release at