Skip to content
Browse files

gis: Merged revisions 7280-7353 via svnmerge from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@7354 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
1 parent b0a895f commit 2efc34dc5e65da792700af6594c537104cbee9dc @jbronn jbronn committed
Showing with 2,256 additions and 1,123 deletions.
  1. +6 −0 AUTHORS
  2. +1 −1 django/conf/global_settings.py
  3. BIN django/conf/locale/de/LC_MESSAGES/django.mo
  4. +557 −390 django/conf/locale/de/LC_MESSAGES/django.po
  5. +27 −27 django/contrib/admin/templates/admin_doc/index.html
  6. +42 −42 django/contrib/admin/templates/admin_doc/view_index.html
  7. +246 −246 django/contrib/formtools/wizard.py
  8. +3 −1 django/contrib/sessions/backends/file.py
  9. +14 −8 django/contrib/syndication/feeds.py
  10. +46 −38 django/core/mail.py
  11. +1 −1 django/core/management/__init__.py
  12. +7 −2 django/core/management/commands/startproject.py
  13. +145 −59 django/core/paginator.py
  14. +4 −6 django/core/serializers/base.py
  15. +12 −2 django/core/servers/fastcgi.py
  16. +10 −0 django/db/models/fields/__init__.py
  17. +7 −0 django/db/models/fields/related.py
  18. +9 −6 django/db/models/query.py
  19. +3 −0 django/http/__init__.py
  20. +23 −5 django/newforms/extras/widgets.py
  21. +29 −1 django/newforms/fields.py
  22. +27 −16 django/newforms/models.py
  23. +4 −1 django/template/defaultfilters.py
  24. +3 −2 django/test/client.py
  25. +4 −2 django/utils/daemonize.py
  26. +14 −3 django/views/debug.py
  27. +27 −23 django/views/generic/list_detail.py
  28. +2 −2 docs/custom_model_fields.txt
  29. +2 −2 docs/db-api.txt
  30. +15 −2 docs/generic_views.txt
  31. +14 −0 docs/install.txt
  32. +11 −9 docs/model-api.txt
  33. +10 −0 docs/modelforms.txt
  34. +75 −7 docs/newforms.txt
  35. +133 −0 docs/pagination.txt
  36. +27 −0 docs/request_response.txt
  37. +4 −4 docs/sessions.txt
  38. +8 −3 docs/settings.txt
  39. +7 −0 docs/syndication_feeds.txt
  40. +86 −78 docs/templates.txt
  41. +6 −0 docs/tutorial04.txt
  42. +5 −3 docs/url_dispatch.txt
  43. +3 −2 tests/modeltests/custom_pk/models.py
  44. +5 −2 tests/modeltests/lookup/models.py
  45. +18 −10 tests/modeltests/manipulators/models.py
  46. +53 −20 tests/modeltests/model_forms/models.py
  47. +172 −27 tests/modeltests/pagination/models.py
  48. +14 −10 tests/modeltests/validation/models.py
  49. +75 −8 tests/regressiontests/forms/extra.py
  50. +27 −0 tests/regressiontests/forms/fields.py
  51. +111 −48 tests/regressiontests/forms/forms.py
  52. +24 −0 tests/regressiontests/model_fields/models.py
  53. 0 tests/regressiontests/syndication/__init__.py
  54. 0 tests/regressiontests/syndication/models.py
  55. +14 −0 tests/regressiontests/syndication/tests.py
  56. +18 −0 tests/regressiontests/syndication/urls.py
  57. +29 −1 tests/regressiontests/test_client_regress/models.py
  58. +1 −0 tests/regressiontests/test_client_regress/urls.py
  59. +13 −3 tests/regressiontests/test_client_regress/views.py
  60. +3 −0 tests/urls.py
View
6 AUTHORS
@@ -59,6 +59,7 @@ answer newbie questions, and generally made Django that much better:
Arthur <avandorp@gmail.com>
David Avsajanishvili <avsd05@gmail.com>
axiak@mit.edu
+ Niran Babalola <niran@niran.org>
Morten Bagai <m@bagai.com>
Mikaël Barbero <mikael.barbero nospam at nospam free.fr>
Jiri Barton
@@ -145,6 +146,7 @@ answer newbie questions, and generally made Django that much better:
Jorge Gajon <gajon@gajon.org>
gandalf@owca.info
Marc Garcia <marc.garcia@accopensys.com>
+ Alex Gaynor <alex.gaynor@gmail.com>
Andy Gayton <andy-django@thecablelounge.com>
Baishampayan Ghose
Dimitris Glezos <dimitris@glezos.com>
@@ -243,6 +245,7 @@ answer newbie questions, and generally made Django that much better:
michael.mcewan@gmail.com
michal@plovarna.cz
Mikko Hellsing <mikko@sorl.net>
+ Daniel Lindsley <polarcowz@gmail.com>
Orestis Markou <orestis@orestis.gr>
Slawek Mikula <slawek dot mikula at gmail dot com>
mitakummaa@gmail.com
@@ -256,6 +259,7 @@ answer newbie questions, and generally made Django that much better:
Robin Munn <http://www.geekforgod.com/>
Robert Myers <myer0052@gmail.com>
Nebojša Dorđević
+ Doug Napoleone <doug@dougma.com>
Gopal Narayanan <gopastro@gmail.com>
Fraser Nevett <mail@nevett.org>
Sam Newman <http://www.magpiebrain.com/>
@@ -269,6 +273,7 @@ answer newbie questions, and generally made Django that much better:
Barry Pederson <bp@barryp.org>
permonik@mesias.brnonet.cz
petr.marhoun@gmail.com
+ peter@mymart.com
pgross@thoughtworks.com
phaedo <http://phaedo.cx/>
phil@produxion.net
@@ -307,6 +312,7 @@ answer newbie questions, and generally made Django that much better:
serbaut@gmail.com
John Shaffer <jshaffer2112@gmail.com>
Pete Shinners <pete@shinners.org>
+ Leo Shklovskii
jason.sidabras@gmail.com
Jozko Skrablin <jozko.skrablin@gmail.com>
Ben Slavin <benjamin.slavin@gmail.com>
View
2 django/conf/global_settings.py
@@ -287,7 +287,7 @@
SESSION_SAVE_EVERY_REQUEST = False # Whether to save the session data on every request.
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Whether sessions expire when a user closes his browser.
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # The module to store session data
-SESSION_FILE_PATH = '/tmp/' # Directory to store session files if using the file session module
+SESSION_FILE_PATH = None # Directory to store session files if using the file session module. If set to None the backend will use a sensible default.
#########
# CACHE #
View
BIN django/conf/locale/de/LC_MESSAGES/django.mo
Binary file not shown.
View
947 django/conf/locale/de/LC_MESSAGES/django.po
557 additions, 390 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
54 django/contrib/admin/templates/admin_doc/index.html
@@ -1,27 +1,27 @@
-{% extends "admin/base_site.html" %}
-{% load i18n %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">Home</a> &rsaquo; Documentation</div>{% endblock %}
-{% block title %}Documentation{% endblock %}
-
-{% block content %}
-
-<h1>Documentation</h1>
-
-<div id="content-main">
- <h3><a href="tags/">Tags</a></h3>
- <p>List of all the template tags and their functions.</p>
-
- <h3><a href="filters/">Filters</a></h3>
- <p>Filters are actions which can be applied to variables in a template to alter the output.</p>
-
- <h3><a href="models/">Models</a></h3>
- <p>Models are descriptions of all the objects in the system and their associated fields. Each model has a list of fields which can be accessed as template variables.</p>
-
- <h3><a href="views/">Views</a></h3>
- <p>Each page on the public site is generated by a view. The view defines which template is used to generate the page and which objects are available to that template.</p>
-
- <h3><a href="bookmarklets/">Bookmarklets</a></h3>
- <p>Tools for your browser to quickly access admin functionality.</p>
-</div>
-
-{% endblock %}
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">Home</a> &rsaquo; Documentation</div>{% endblock %}
+{% block title %}Documentation{% endblock %}
+
+{% block content %}
+
+<h1>Documentation</h1>
+
+<div id="content-main">
+ <h3><a href="tags/">Tags</a></h3>
+ <p>List of all the template tags and their functions.</p>
+
+ <h3><a href="filters/">Filters</a></h3>
+ <p>Filters are actions which can be applied to variables in a template to alter the output.</p>
+
+ <h3><a href="models/">Models</a></h3>
+ <p>Models are descriptions of all the objects in the system and their associated fields. Each model has a list of fields which can be accessed as template variables.</p>
+
+ <h3><a href="views/">Views</a></h3>
+ <p>Each page on the public site is generated by a view. The view defines which template is used to generate the page and which objects are available to that template.</p>
+
+ <h3><a href="bookmarklets/">Bookmarklets</a></h3>
+ <p>Tools for your browser to quickly access admin functionality.</p>
+</div>
+
+{% endblock %}
View
84 django/contrib/admin/templates/admin_doc/view_index.html
@@ -1,42 +1,42 @@
-{% extends "admin/base_site.html" %}
-{% load i18n %}
-{% block coltype %}colSM{% endblock %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; Views</div>{% endblock %}
-{% block title %}Views{% endblock %}
-
-{% block content %}
-
-<h1>View documentation</h1>
-
-{% regroup views|dictsort:"site_id" by site as views_by_site %}
-
-<div id="content-related" class="sidebar">
-<div class="module">
-<h2>Jump to site</h2>
-<ul>
- {% for site_views in views_by_site %}
- <li><a href="#site{{ site_views.grouper.id }}">{{ site_views.grouper.name }}</a></li>
- {% endfor %}
-</ul>
-</div>
-</div>
-
-<div id="content-main">
-
-{% for site_views in views_by_site %}
-<div class="module">
-<h2 id="site{{ site_views.grouper.id }}">Views by URL on {{ site_views.grouper.name }}</h2>
-
-{% for view in site_views.list|dictsort:"url" %}
-{% ifchanged %}
-<h3><a href="{{ view.module }}.{{ view.name }}/">{{ view.url|escape }}</a></h3>
-<p class="small quiet">View function: {{ view.module }}.{{ view.name }}</p>
-<p>{{ view.title }}</p>
-<hr />
-{% endifchanged %}
-{% endfor %}
-</div>
-{% endfor %}
-</div>
-{% endblock %}
-
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+{% block coltype %}colSM{% endblock %}
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; Views</div>{% endblock %}
+{% block title %}Views{% endblock %}
+
+{% block content %}
+
+<h1>View documentation</h1>
+
+{% regroup views|dictsort:"site_id" by site as views_by_site %}
+
+<div id="content-related" class="sidebar">
+<div class="module">
+<h2>Jump to site</h2>
+<ul>
+ {% for site_views in views_by_site %}
+ <li><a href="#site{{ site_views.grouper.id }}">{{ site_views.grouper.name }}</a></li>
+ {% endfor %}
+</ul>
+</div>
+</div>
+
+<div id="content-main">
+
+{% for site_views in views_by_site %}
+<div class="module">
+<h2 id="site{{ site_views.grouper.id }}">Views by URL on {{ site_views.grouper.name }}</h2>
+
+{% for view in site_views.list|dictsort:"url" %}
+{% ifchanged %}
+<h3><a href="{{ view.module }}.{{ view.name }}/">{{ view.url|escape }}</a></h3>
+<p class="small quiet">View function: {{ view.module }}.{{ view.name }}</p>
+<p>{{ view.title }}</p>
+<hr />
+{% endifchanged %}
+{% endfor %}
+</div>
+{% endfor %}
+</div>
+{% endblock %}
+
View
492 django/contrib/formtools/wizard.py
@@ -1,246 +1,246 @@
-"""
-FormWizard class -- implements a multi-page form, validating between each
-step and storing the form's state as HTML hidden fields so that no state is
-stored on the server side.
-"""
-
-from django import newforms as forms
-from django.conf import settings
-from django.http import Http404
-from django.shortcuts import render_to_response
-from django.template.context import RequestContext
-import cPickle as pickle
-import md5
-
-class FormWizard(object):
- # Dictionary of extra template context variables.
- extra_context = {}
-
- # The HTML (and POST data) field name for the "step" variable.
- step_field_name="wizard_step"
-
- # METHODS SUBCLASSES SHOULDN'T OVERRIDE ###################################
-
- def __init__(self, form_list, initial=None):
- "form_list should be a list of Form classes (not instances)."
- self.form_list = form_list[:]
- self.initial = initial or {}
- self.step = 0 # A zero-based counter keeping track of which step we're in.
-
- def __repr__(self):
- return "step: %d\nform_list: %s\ninitial_data: %s" % (self.step, self.form_list, self.initial)
-
- def get_form(self, step, data=None):
- "Helper method that returns the Form instance for the given step."
- return self.form_list[step](data, prefix=self.prefix_for_step(step), initial=self.initial.get(step, None))
-
- def num_steps(self):
- "Helper method that returns the number of steps."
- # You might think we should just set "self.form_list = len(form_list)"
- # in __init__(), but this calculation needs to be dynamic, because some
- # hook methods might alter self.form_list.
- return len(self.form_list)
-
- def __call__(self, request, *args, **kwargs):
- """
- Main method that does all the hard work, conforming to the Django view
- interface.
- """
- if 'extra_context' in kwargs:
- self.extra_context.update(kwargs['extra_context'])
- current_step = self.determine_step(request, *args, **kwargs)
- self.parse_params(request, *args, **kwargs)
-
- # Sanity check.
- if current_step >= self.num_steps():
- raise Http404('Step %s does not exist' % current_step)
-
- # For each previous step, verify the hash and process.
- # TODO: Move "hash_%d" to a method to make it configurable.
- for i in range(current_step):
- form = self.get_form(i, request.POST)
- if request.POST.get("hash_%d" % i, '') != self.security_hash(request, form):
- return self.render_hash_failure(request, i)
- self.process_step(request, form, i)
-
- # Process the current step. If it's valid, go to the next step or call
- # done(), depending on whether any steps remain.
- if request.method == 'POST':
- form = self.get_form(current_step, request.POST)
- else:
- form = self.get_form(current_step)
- if form.is_valid():
- self.process_step(request, form, current_step)
- next_step = current_step + 1
-
- # If this was the last step, validate all of the forms one more
- # time, as a sanity check, and call done().
- num = self.num_steps()
- if next_step == num:
- final_form_list = [self.get_form(i, request.POST) for i in range(num)]
-
- # Validate all the forms. If any of them fail validation, that
- # must mean the validator relied on some other input, such as
- # an external Web site.
- for i, f in enumerate(final_form_list):
- if not f.is_valid():
- return self.render_revalidation_failure(request, i, f)
- return self.done(request, final_form_list)
-
- # Otherwise, move along to the next step.
- else:
- form = self.get_form(next_step)
- current_step = next_step
-
- return self.render(form, request, current_step)
-
- def render(self, form, request, step, context=None):
- "Renders the given Form object, returning an HttpResponse."
- old_data = request.POST
- prev_fields = []
- if old_data:
- hidden = forms.HiddenInput()
- # Collect all data from previous steps and render it as HTML hidden fields.
- for i in range(step):
- old_form = self.get_form(i, old_data)
- hash_name = 'hash_%s' % i
- prev_fields.extend([bf.as_hidden() for bf in old_form])
- prev_fields.append(hidden.render(hash_name, old_data.get(hash_name, self.security_hash(request, old_form))))
- return self.render_template(request, form, ''.join(prev_fields), step, context)
-
- # METHODS SUBCLASSES MIGHT OVERRIDE IF APPROPRIATE ########################
-
- def prefix_for_step(self, step):
- "Given the step, returns a Form prefix to use."
- return str(step)
-
- def render_hash_failure(self, request, step):
- """
- Hook for rendering a template if a hash check failed.
-
- step is the step that failed. Any previous step is guaranteed to be
- valid.
-
- This default implementation simply renders the form for the given step,
- but subclasses may want to display an error message, etc.
- """
- return self.render(self.get_form(step), request, step, context={'wizard_error': 'We apologize, but your form has expired. Please continue filling out the form from this page.'})
-
- def render_revalidation_failure(self, request, step, form):
- """
- Hook for rendering a template if final revalidation failed.
-
- It is highly unlikely that this point would ever be reached, but See
- the comment in __call__() for an explanation.
- """
- return self.render(form, request, step)
-
- def security_hash(self, request, form):
- """
- Calculates the security hash for the given HttpRequest and Form instances.
-
- This creates a list of the form field names/values in a deterministic
- order, pickles the result with the SECRET_KEY setting and takes an md5
- hash of that.
-
- Subclasses may want to take into account request-specific information,
- such as the IP address.
- """
- data = [(bf.name, bf.data or '') for bf in form] + [settings.SECRET_KEY]
- # Use HIGHEST_PROTOCOL because it's the most efficient. It requires
- # Python 2.3, but Django requires 2.3 anyway, so that's OK.
- pickled = pickle.dumps(data, protocol=pickle.HIGHEST_PROTOCOL)
- return md5.new(pickled).hexdigest()
-
- def determine_step(self, request, *args, **kwargs):
- """
- Given the request object and whatever *args and **kwargs were passed to
- __call__(), returns the current step (which is zero-based).
-
- Note that the result should not be trusted. It may even be a completely
- invalid number. It's not the job of this method to validate it.
- """
- if not request.POST:
- return 0
- try:
- step = int(request.POST.get(self.step_field_name, 0))
- except ValueError:
- return 0
- return step
-
- def parse_params(self, request, *args, **kwargs):
- """
- Hook for setting some state, given the request object and whatever
- *args and **kwargs were passed to __call__(), sets some state.
-
- This is called at the beginning of __call__().
- """
- pass
-
- def get_template(self, step):
- """
- Hook for specifying the name of the template to use for a given step.
-
- Note that this can return a tuple of template names if you'd like to
- use the template system's select_template() hook.
- """
- return 'forms/wizard.html'
-
- def render_template(self, request, form, previous_fields, step, context=None):
- """
- Renders the template for the given step, returning an HttpResponse object.
-
- Override this method if you want to add a custom context, return a
- different MIME type, etc. If you only need to override the template
- name, use get_template() instead.
-
- The template will be rendered with the following context:
- step_field -- The name of the hidden field containing the step.
- step0 -- The current step (zero-based).
- step -- The current step (one-based).
- step_count -- The total number of steps.
- form -- The Form instance for the current step (either empty
- or with errors).
- previous_fields -- A string representing every previous data field,
- plus hashes for completed forms, all in the form of
- hidden fields. Note that you'll need to run this
- through the "safe" template filter, to prevent
- auto-escaping, because it's raw HTML.
- """
- context = context or {}
- context.update(self.extra_context)
- return render_to_response(self.get_template(self.step), dict(context,
- step_field=self.step_field_name,
- step0=step,
- step=step + 1,
- step_count=self.num_steps(),
- form=form,
- previous_fields=previous_fields
- ), context_instance=RequestContext(request))
-
- def process_step(self, request, form, step):
- """
- Hook for modifying the FormWizard's internal state, given a fully
- validated Form object. The Form is guaranteed to have clean, valid
- data.
-
- This method should *not* modify any of that data. Rather, it might want
- to set self.extra_context or dynamically alter self.form_list, based on
- previously submitted forms.
-
- Note that this method is called every time a page is rendered for *all*
- submitted steps.
- """
- pass
-
- # METHODS SUBCLASSES MUST OVERRIDE ########################################
-
- def done(self, request, form_list):
- """
- Hook for doing something with the validated data. This is responsible
- for the final processing.
-
- form_list is a list of Form instances, each containing clean, valid
- data.
- """
- raise NotImplementedError("Your %s class has not defined a done() method, which is required." % self.__class__.__name__)
+"""
+FormWizard class -- implements a multi-page form, validating between each
+step and storing the form's state as HTML hidden fields so that no state is
+stored on the server side.
+"""
+
+from django import newforms as forms
+from django.conf import settings
+from django.http import Http404
+from django.shortcuts import render_to_response
+from django.template.context import RequestContext
+import cPickle as pickle
+import md5
+
+class FormWizard(object):
+ # Dictionary of extra template context variables.
+ extra_context = {}
+
+ # The HTML (and POST data) field name for the "step" variable.
+ step_field_name="wizard_step"
+
+ # METHODS SUBCLASSES SHOULDN'T OVERRIDE ###################################
+
+ def __init__(self, form_list, initial=None):
+ "form_list should be a list of Form classes (not instances)."
+ self.form_list = form_list[:]
+ self.initial = initial or {}
+ self.step = 0 # A zero-based counter keeping track of which step we're in.
+
+ def __repr__(self):
+ return "step: %d\nform_list: %s\ninitial_data: %s" % (self.step, self.form_list, self.initial)
+
+ def get_form(self, step, data=None):
+ "Helper method that returns the Form instance for the given step."
+ return self.form_list[step](data, prefix=self.prefix_for_step(step), initial=self.initial.get(step, None))
+
+ def num_steps(self):
+ "Helper method that returns the number of steps."
+ # You might think we should just set "self.form_list = len(form_list)"
+ # in __init__(), but this calculation needs to be dynamic, because some
+ # hook methods might alter self.form_list.
+ return len(self.form_list)
+
+ def __call__(self, request, *args, **kwargs):
+ """
+ Main method that does all the hard work, conforming to the Django view
+ interface.
+ """
+ if 'extra_context' in kwargs:
+ self.extra_context.update(kwargs['extra_context'])
+ current_step = self.determine_step(request, *args, **kwargs)
+ self.parse_params(request, *args, **kwargs)
+
+ # Sanity check.
+ if current_step >= self.num_steps():
+ raise Http404('Step %s does not exist' % current_step)
+
+ # For each previous step, verify the hash and process.
+ # TODO: Move "hash_%d" to a method to make it configurable.
+ for i in range(current_step):
+ form = self.get_form(i, request.POST)
+ if request.POST.get("hash_%d" % i, '') != self.security_hash(request, form):
+ return self.render_hash_failure(request, i)
+ self.process_step(request, form, i)
+
+ # Process the current step. If it's valid, go to the next step or call
+ # done(), depending on whether any steps remain.
+ if request.method == 'POST':
+ form = self.get_form(current_step, request.POST)
+ else:
+ form = self.get_form(current_step)
+ if form.is_valid():
+ self.process_step(request, form, current_step)
+ next_step = current_step + 1
+
+ # If this was the last step, validate all of the forms one more
+ # time, as a sanity check, and call done().
+ num = self.num_steps()
+ if next_step == num:
+ final_form_list = [self.get_form(i, request.POST) for i in range(num)]
+
+ # Validate all the forms. If any of them fail validation, that
+ # must mean the validator relied on some other input, such as
+ # an external Web site.
+ for i, f in enumerate(final_form_list):
+ if not f.is_valid():
+ return self.render_revalidation_failure(request, i, f)
+ return self.done(request, final_form_list)
+
+ # Otherwise, move along to the next step.
+ else:
+ form = self.get_form(next_step)
+ current_step = next_step
+
+ return self.render(form, request, current_step)
+
+ def render(self, form, request, step, context=None):
+ "Renders the given Form object, returning an HttpResponse."
+ old_data = request.POST
+ prev_fields = []
+ if old_data:
+ hidden = forms.HiddenInput()
+ # Collect all data from previous steps and render it as HTML hidden fields.
+ for i in range(step):
+ old_form = self.get_form(i, old_data)
+ hash_name = 'hash_%s' % i
+ prev_fields.extend([bf.as_hidden() for bf in old_form])
+ prev_fields.append(hidden.render(hash_name, old_data.get(hash_name, self.security_hash(request, old_form))))
+ return self.render_template(request, form, ''.join(prev_fields), step, context)
+
+ # METHODS SUBCLASSES MIGHT OVERRIDE IF APPROPRIATE ########################
+
+ def prefix_for_step(self, step):
+ "Given the step, returns a Form prefix to use."
+ return str(step)
+
+ def render_hash_failure(self, request, step):
+ """
+ Hook for rendering a template if a hash check failed.
+
+ step is the step that failed. Any previous step is guaranteed to be
+ valid.
+
+ This default implementation simply renders the form for the given step,
+ but subclasses may want to display an error message, etc.
+ """
+ return self.render(self.get_form(step), request, step, context={'wizard_error': 'We apologize, but your form has expired. Please continue filling out the form from this page.'})
+
+ def render_revalidation_failure(self, request, step, form):
+ """
+ Hook for rendering a template if final revalidation failed.
+
+ It is highly unlikely that this point would ever be reached, but See
+ the comment in __call__() for an explanation.
+ """
+ return self.render(form, request, step)
+
+ def security_hash(self, request, form):
+ """
+ Calculates the security hash for the given HttpRequest and Form instances.
+
+ This creates a list of the form field names/values in a deterministic
+ order, pickles the result with the SECRET_KEY setting and takes an md5
+ hash of that.
+
+ Subclasses may want to take into account request-specific information,
+ such as the IP address.
+ """
+ data = [(bf.name, bf.data or '') for bf in form] + [settings.SECRET_KEY]
+ # Use HIGHEST_PROTOCOL because it's the most efficient. It requires
+ # Python 2.3, but Django requires 2.3 anyway, so that's OK.
+ pickled = pickle.dumps(data, protocol=pickle.HIGHEST_PROTOCOL)
+ return md5.new(pickled).hexdigest()
+
+ def determine_step(self, request, *args, **kwargs):
+ """
+ Given the request object and whatever *args and **kwargs were passed to
+ __call__(), returns the current step (which is zero-based).
+
+ Note that the result should not be trusted. It may even be a completely
+ invalid number. It's not the job of this method to validate it.
+ """
+ if not request.POST:
+ return 0
+ try:
+ step = int(request.POST.get(self.step_field_name, 0))
+ except ValueError:
+ return 0
+ return step
+
+ def parse_params(self, request, *args, **kwargs):
+ """
+ Hook for setting some state, given the request object and whatever
+ *args and **kwargs were passed to __call__(), sets some state.
+
+ This is called at the beginning of __call__().
+ """
+ pass
+
+ def get_template(self, step):
+ """
+ Hook for specifying the name of the template to use for a given step.
+
+ Note that this can return a tuple of template names if you'd like to
+ use the template system's select_template() hook.
+ """
+ return 'forms/wizard.html'
+
+ def render_template(self, request, form, previous_fields, step, context=None):
+ """
+ Renders the template for the given step, returning an HttpResponse object.
+
+ Override this method if you want to add a custom context, return a
+ different MIME type, etc. If you only need to override the template
+ name, use get_template() instead.
+
+ The template will be rendered with the following context:
+ step_field -- The name of the hidden field containing the step.
+ step0 -- The current step (zero-based).
+ step -- The current step (one-based).
+ step_count -- The total number of steps.
+ form -- The Form instance for the current step (either empty
+ or with errors).
+ previous_fields -- A string representing every previous data field,
+ plus hashes for completed forms, all in the form of
+ hidden fields. Note that you'll need to run this
+ through the "safe" template filter, to prevent
+ auto-escaping, because it's raw HTML.
+ """
+ context = context or {}
+ context.update(self.extra_context)
+ return render_to_response(self.get_template(self.step), dict(context,
+ step_field=self.step_field_name,
+ step0=step,
+ step=step + 1,
+ step_count=self.num_steps(),
+ form=form,
+ previous_fields=previous_fields
+ ), context_instance=RequestContext(request))
+
+ def process_step(self, request, form, step):
+ """
+ Hook for modifying the FormWizard's internal state, given a fully
+ validated Form object. The Form is guaranteed to have clean, valid
+ data.
+
+ This method should *not* modify any of that data. Rather, it might want
+ to set self.extra_context or dynamically alter self.form_list, based on
+ previously submitted forms.
+
+ Note that this method is called every time a page is rendered for *all*
+ submitted steps.
+ """
+ pass
+
+ # METHODS SUBCLASSES MUST OVERRIDE ########################################
+
+ def done(self, request, form_list):
+ """
+ Hook for doing something with the validated data. This is responsible
+ for the final processing.
+
+ form_list is a list of Form instances, each containing clean, valid
+ data.
+ """
+ raise NotImplementedError("Your %s class has not defined a done() method, which is required." % self.__class__.__name__)
View
4 django/contrib/sessions/backends/file.py
@@ -9,7 +9,9 @@ class SessionStore(SessionBase):
Implements a file based session store.
"""
def __init__(self, session_key=None):
- self.storage_path = getattr(settings, "SESSION_FILE_PATH", tempfile.gettempdir())
+ self.storage_path = getattr(settings, "SESSION_FILE_PATH", None)
+ if not self.storage_path:
+ self.storage_path = tempfile.gettempdir()
# Make sure the storage path is valid.
if not os.path.isdir(self.storage_path):
View
22 django/contrib/syndication/feeds.py
@@ -3,7 +3,8 @@
from django.contrib.sites.models import Site, RequestSite
from django.utils import feedgenerator
from django.utils.encoding import smart_unicode, iri_to_uri
-from django.conf import settings
+from django.conf import settings
+from django.template import RequestContext
def add_domain(domain, url):
if not (url.startswith('http://') or url.startswith('https://')):
@@ -55,18 +56,23 @@ def __get_dynamic_attr(self, attname, obj, default=None):
return attr()
return attr
+ def get_object(self, bits):
+ return None
+
def get_feed(self, url=None):
"""
Returns a feedgenerator.DefaultFeed object, fully populated, for
this feed. Raises FeedDoesNotExist for invalid parameters.
"""
if url:
- try:
- obj = self.get_object(url.split('/'))
- except (AttributeError, ObjectDoesNotExist):
- raise FeedDoesNotExist
+ bits = url.split('/')
else:
- obj = None
+ bits = []
+
+ try:
+ obj = self.get_object(bits)
+ except ObjectDoesNotExist:
+ raise FeedDoesNotExist
if Site._meta.installed:
current_site = Site.objects.get_current()
@@ -119,9 +125,9 @@ def get_feed(self, url=None):
else:
author_email = author_link = None
feed.add_item(
- title = title_tmp.render(Context({'obj': item, 'site': current_site})),
+ title = title_tmp.render(RequestContext(self.request, {'obj': item, 'site': current_site})),
link = link,
- description = description_tmp.render(Context({'obj': item, 'site': current_site})),
+ description = description_tmp.render(RequestContext(self.request, {'obj': item, 'site': current_site})),
unique_id = self.__get_dynamic_attr('item_guid', item, link),
enclosure = enc,
pubdate = self.__get_dynamic_attr('item_pubdate', item),
View
84 django/core/mail.py
@@ -2,20 +2,21 @@
Tools for sending email.
"""
-from django.conf import settings
-from django.utils.encoding import smart_str, force_unicode
-from email import Charset, Encoders
-from email.MIMEText import MIMEText
-from email.MIMEMultipart import MIMEMultipart
-from email.MIMEBase import MIMEBase
-from email.Header import Header
-from email.Utils import formatdate, parseaddr, formataddr
import mimetypes
import os
import smtplib
import socket
import time
import random
+from email import Charset, Encoders
+from email.MIMEText import MIMEText
+from email.MIMEMultipart import MIMEMultipart
+from email.MIMEBase import MIMEBase
+from email.Header import Header
+from email.Utils import formatdate, parseaddr, formataddr
+
+from django.conf import settings
+from django.utils.encoding import smart_str, force_unicode
# Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
# some spam filters.
@@ -38,8 +39,9 @@ def get_fqdn(self):
DNS_NAME = CachedDnsName()
-# Copied from Python standard library and modified to used the cached hostname
-# for performance.
+# Copied from Python standard library, with the following modifications:
+# * Used cached hostname for performance.
+# * Added try/except to support lack of getpid() in Jython (#5496).
def make_msgid(idstring=None):
"""Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
@@ -53,7 +55,7 @@ def make_msgid(idstring=None):
try:
pid = os.getpid()
except AttributeError:
- # Not getpid() in Jython, for example.
+ # No getpid() in Jython, for example.
pid = 1
randint = random.randrange(100000)
if idstring is None:
@@ -68,7 +70,7 @@ class BadHeaderError(ValueError):
pass
def forbid_multi_line_headers(name, val):
- "Forbids multi-line headers, to prevent header injection."
+ """Forbids multi-line headers, to prevent header injection."""
if '\n' in val or '\r' in val:
raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
try:
@@ -101,7 +103,7 @@ class SMTPConnection(object):
"""
def __init__(self, host=None, port=None, username=None, password=None,
- use_tls=None, fail_silently=False):
+ use_tls=None, fail_silently=False):
self.host = host or settings.EMAIL_HOST
self.port = port or settings.EMAIL_PORT
self.username = username or settings.EMAIL_HOST_USER
@@ -112,14 +114,17 @@ def __init__(self, host=None, port=None, username=None, password=None,
def open(self):
"""
- Ensure we have a connection to the email server. Returns whether or not
- a new connection was required.
+ Ensures we have a connection to the email server. Returns whether or
+ not a new connection was required (True or False).
"""
if self.connection:
# Nothing to do if the connection is already open.
return False
try:
- self.connection = smtplib.SMTP(self.host, self.port)
+ # If local_hostname is not specified, socket.getfqdn() gets used.
+ # For performance, we use the cached FQDN for local_hostname.
+ self.connection = smtplib.SMTP(self.host, self.port,
+ local_hostname=DNS_NAME.get_fqdn())
if self.use_tls:
self.connection.ehlo()
self.connection.starttls()
@@ -132,7 +137,7 @@ def open(self):
raise
def close(self):
- """Close the connection to the email server."""
+ """Closes the connection to the email server."""
try:
try:
self.connection.quit()
@@ -149,7 +154,7 @@ def close(self):
def send_messages(self, email_messages):
"""
- Send one or more EmailMessage objects and return the number of email
+ Sends one or more EmailMessage objects and returns the number of email
messages sent.
"""
if not email_messages:
@@ -192,7 +197,7 @@ class EmailMessage(object):
def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
connection=None, attachments=None, headers=None):
"""
- Initialise a single email message (which can be sent to multiple
+ Initialize a single email message (which can be sent to multiple
recipients).
All strings used to create the message can be unicode strings (or UTF-8
@@ -221,7 +226,8 @@ def get_connection(self, fail_silently=False):
def message(self):
encoding = self.encoding or settings.DEFAULT_CHARSET
- msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET), self.content_subtype, encoding)
+ msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
+ self.content_subtype, encoding)
if self.attachments:
body_msg = msg
msg = SafeMIMEMultipart(_subtype=self.multipart_subtype)
@@ -237,8 +243,6 @@ def message(self):
msg['To'] = ', '.join(self.to)
msg['Date'] = formatdate()
msg['Message-ID'] = make_msgid()
- if self.bcc:
- msg['Bcc'] = ', '.join(self.bcc)
for name, value in self.extra_headers.items():
msg[name] = value
return msg
@@ -251,7 +255,7 @@ def recipients(self):
return self.to + self.bcc
def send(self, fail_silently=False):
- """Send the email message."""
+ """Sends the email message."""
return self.get_connection(fail_silently).send_messages([self])
def attach(self, filename=None, content=None, mimetype=None):
@@ -278,7 +282,7 @@ def attach_file(self, path, mimetype=None):
def _create_attachment(self, filename, content, mimetype=None):
"""
- Convert the filename, content, mimetype triple into a MIME attachment
+ Converts the filename, content, mimetype triple into a MIME attachment
object.
"""
if mimetype is None:
@@ -295,7 +299,8 @@ def _create_attachment(self, filename, content, mimetype=None):
attachment.set_payload(content)
Encoders.encode_base64(attachment)
if filename:
- attachment.add_header('Content-Disposition', 'attachment', filename=filename)
+ attachment.add_header('Content-Disposition', 'attachment',
+ filename=filename)
return attachment
class EmailMultiAlternatives(EmailMessage):
@@ -310,7 +315,8 @@ def attach_alternative(self, content, mimetype=None):
"""Attach an alternative content representation."""
self.attach(content=content, mimetype=mimetype)
-def send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None):
+def send_mail(subject, message, from_email, recipient_list,
+ fail_silently=False, auth_user=None, auth_password=None):
"""
Easy wrapper for sending a single message to a recipient list. All members
of the recipient list will see the other recipients in the 'To' field.
@@ -322,10 +328,12 @@ def send_mail(subject, message, from_email, recipient_list, fail_silently=False,
functionality should use the EmailMessage class directly.
"""
connection = SMTPConnection(username=auth_user, password=auth_password,
- fail_silently=fail_silently)
- return EmailMessage(subject, message, from_email, recipient_list, connection=connection).send()
+ fail_silently=fail_silently)
+ return EmailMessage(subject, message, from_email, recipient_list,
+ connection=connection).send()
-def send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password=None):
+def send_mass_mail(datatuple, fail_silently=False, auth_user=None,
+ auth_password=None):
"""
Given a datatuple of (subject, message, from_email, recipient_list), sends
each message to each recipient list. Returns the number of e-mails sent.
@@ -339,19 +347,19 @@ def send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password
functionality should use the EmailMessage class directly.
"""
connection = SMTPConnection(username=auth_user, password=auth_password,
- fail_silently=fail_silently)
- messages = [EmailMessage(subject, message, sender, recipient) for subject, message, sender, recipient in datatuple]
+ fail_silently=fail_silently)
+ messages = [EmailMessage(subject, message, sender, recipient)
+ for subject, message, sender, recipient in datatuple]
return connection.send_messages(messages)
def mail_admins(subject, message, fail_silently=False):
- "Sends a message to the admins, as defined by the ADMINS setting."
+ """Sends a message to the admins, as defined by the ADMINS setting."""
EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
- settings.SERVER_EMAIL, [a[1] for a in
- settings.ADMINS]).send(fail_silently=fail_silently)
+ settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS]
+ ).send(fail_silently=fail_silently)
def mail_managers(subject, message, fail_silently=False):
- "Sends a message to the managers, as defined by the MANAGERS setting."
+ """Sends a message to the managers, as defined by the MANAGERS setting."""
EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
- settings.SERVER_EMAIL, [a[1] for a in
- settings.MANAGERS]).send(fail_silently=fail_silently)
-
+ settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS]
+ ).send(fail_silently=fail_silently)
View
2 django/core/management/__init__.py
@@ -243,7 +243,7 @@ def setup_environ(settings_mod):
# way. For example, if this file (manage.py) lives in a directory
# "myproject", this code would add "/path/to/myproject" to sys.path.
project_directory, settings_filename = os.path.split(settings_mod.__file__)
- if not project_directory:
+ if project_directory == os.curdir or not project_directory:
project_directory = os.getcwd()
project_name = os.path.basename(project_directory)
settings_name = os.path.splitext(settings_filename)[0]
View
9 django/core/management/commands/startproject.py
@@ -20,8 +20,13 @@ def handle_label(self, project_name, **options):
# the parent directory.
directory = os.getcwd()
- if project_name in INVALID_PROJECT_NAMES:
- raise CommandError("%r conflicts with the name of an existing Python module and cannot be used as a project name. Please try another name." % project_name)
+ try:
+ proj_name = __import__(project_name)
+ if proj_name:
+ raise CommandError("%r conflicts with the name of an existing Python module and cannot be used as a project name. Please try another name." % project_name)
+ except ImportError:
+ if project_name in INVALID_PROJECT_NAMES:
+ raise CommandError("%r contains an invalid project name. Please try another name." % project_name)
copy_helper(self.style, 'project', project_name, directory)
View
204 django/core/paginator.py
@@ -1,46 +1,149 @@
class InvalidPage(Exception):
pass
-class ObjectPaginator(object):
+class Paginator(object):
+ def __init__(self, object_list, per_page, orphans=0, allow_empty_first_page=True):
+ self.object_list = object_list
+ self.per_page = per_page
+ self.orphans = orphans
+ self.allow_empty_first_page = allow_empty_first_page
+ self._num_pages = self._count = None
+
+ def validate_number(self, number):
+ "Validates the given 1-based page number."
+ try:
+ number = int(number)
+ except ValueError:
+ raise InvalidPage('That page number is not an integer')
+ if number < 1:
+ raise InvalidPage('That page number is less than 1')
+ if number > self.num_pages:
+ if number == 1 and self.allow_empty_first_page:
+ pass
+ else:
+ raise InvalidPage('That page contains no results')
+ return number
+
+ def page(self, number):
+ "Returns a Page object for the given 1-based page number."
+ number = self.validate_number(number)
+ bottom = (number - 1) * self.per_page
+ top = bottom + self.per_page
+ if top + self.orphans >= self.count:
+ top = self.count
+ return Page(self.object_list[bottom:top], number, self)
+
+ def _get_count(self):
+ "Returns the total number of objects, across all pages."
+ if self._count is None:
+ self._count = len(self.object_list)
+ return self._count
+ count = property(_get_count)
+
+ def _get_num_pages(self):
+ "Returns the total number of pages."
+ if self._num_pages is None:
+ hits = self.count - 1 - self.orphans
+ if hits < 1:
+ hits = 0
+ if hits == 0 and not self.allow_empty_first_page:
+ self._num_pages = 0
+ else:
+ self._num_pages = hits // self.per_page + 1
+ return self._num_pages
+ num_pages = property(_get_num_pages)
+
+ def _get_page_range(self):
+ """
+ Returns a 1-based range of pages for iterating through within
+ a template for loop.
+ """
+ return range(1, self.num_pages + 1)
+ page_range = property(_get_page_range)
+
+class QuerySetPaginator(Paginator):
"""
- This class makes pagination easy. Feed it a QuerySet or list, plus the number
- of objects you want on each page. Then read the hits and pages properties to
- see how many pages it involves. Call get_page with a page number (starting
- at 0) to get back a list of objects for that page.
-
- Finally, check if a page number has a next/prev page using
- has_next_page(page_number) and has_previous_page(page_number).
-
- Use orphans to avoid small final pages. For example:
- 13 records, num_per_page=10, orphans=2 --> pages==2, len(self.get_page(0))==10
- 12 records, num_per_page=10, orphans=2 --> pages==1, len(self.get_page(0))==12
+ Like Paginator, but works on QuerySets.
+ """
+ def _get_count(self):
+ if self._count is None:
+ self._count = self.object_list.count()
+ return self._count
+ count = property(_get_count)
+
+class Page(object):
+ def __init__(self, object_list, number, paginator):
+ self.object_list = object_list
+ self.number = number
+ self.paginator = paginator
+
+ def __repr__(self):
+ return '<Page %s of %s>' % (self.number, self.paginator.num_pages)
+
+ def has_next(self):
+ return self.number < self.paginator.num_pages
+
+ def has_previous(self):
+ return self.number > 1
+
+ def has_other_pages(self):
+ return self.has_previous() or self.has_next()
+
+ def next_page_number(self):
+ return self.number + 1
+
+ def previous_page_number(self):
+ return self.number - 1
+
+ def start_index(self):
+ """
+ Returns the 1-based index of the first object on this page,
+ relative to total objects in the paginator.
+ """
+ return (self.paginator.per_page * (self.number - 1)) + 1
+
+ def end_index(self):
+ """
+ Returns the 1-based index of the last object on this page,
+ relative to total objects found (hits).
+ """
+ if self.number == self.paginator.num_pages:
+ return self.paginator.count
+ return self.number * self.paginator.per_page
+
+class ObjectPaginator(Paginator):
+ """
+ Legacy ObjectPaginator class, for backwards compatibility.
+
+ Note that each method on this class that takes page_number expects a
+ zero-based page number, whereas the new API (Paginator/Page) uses one-based
+ page numbers.
"""
def __init__(self, query_set, num_per_page, orphans=0):
+ Paginator.__init__(self, query_set, num_per_page, orphans)
+ import warnings
+ warnings.warn("The ObjectPaginator is deprecated. Use django.core.paginator.Paginator instead.", DeprecationWarning)
+
+ # Keep these attributes around for backwards compatibility.
self.query_set = query_set
self.num_per_page = num_per_page
- self.orphans = orphans
self._hits = self._pages = None
- self._page_range = None
def validate_page_number(self, page_number):
try:
- page_number = int(page_number)
+ page_number = int(page_number) + 1
except ValueError:
raise InvalidPage
- if page_number < 0 or page_number > self.pages - 1:
- raise InvalidPage
- return page_number
+ return self.validate_number(page_number)
def get_page(self, page_number):
- page_number = self.validate_page_number(page_number)
- bottom = page_number * self.num_per_page
- top = bottom + self.num_per_page
- if top + self.orphans >= self.hits:
- top = self.hits
- return self.query_set[bottom:top]
+ try:
+ page_number = int(page_number) + 1
+ except ValueError:
+ raise InvalidPage
+ return self.page(page_number).object_list
def has_next_page(self, page_number):
- "Does page $page_number have a 'next' page?"
return page_number < self.pages - 1
def has_previous_page(self, page_number):
@@ -52,7 +155,7 @@ def first_on_page(self, page_number):
relative to total objects found (hits).
"""
page_number = self.validate_page_number(page_number)
- return (self.num_per_page * page_number) + 1
+ return (self.num_per_page * (page_number - 1)) + 1
def last_on_page(self, page_number):
"""
@@ -60,40 +163,23 @@ def last_on_page(self, page_number):
relative to total objects found (hits).
"""
page_number = self.validate_page_number(page_number)
- page_number += 1 # 1-base
- if page_number == self.pages:
- return self.hits
+ if page_number == self.num_pages:
+ return self.count
return page_number * self.num_per_page
- def _get_hits(self):
- if self._hits is None:
- # Try .count() or fall back to len().
+ def _get_count(self):
+ # The old API allowed for self.object_list to be either a QuerySet or a
+ # list. Here, we handle both.
+ if self._count is None:
try:
- self._hits = int(self.query_set.count())
- except (AttributeError, TypeError, ValueError):
- # AttributeError if query_set has no object count.
- # TypeError if query_set.count() required arguments.
- # ValueError if int() fails.
- self._hits = len(self.query_set)
- return self._hits
-
- def _get_pages(self):
- if self._pages is None:
- hits = (self.hits - 1 - self.orphans)
- if hits < 1:
- hits = 0
- self._pages = hits // self.num_per_page + 1
- return self._pages
-
- def _get_page_range(self):
- """
- Returns a 1-based range of pages for iterating through within
- a template for loop.
- """
- if self._page_range is None:
- self._page_range = range(1, self.pages + 1)
- return self._page_range
+ self._count = self.object_list.count()
+ except TypeError:
+ self._count = len(self.object_list)
+ return self._count
+ count = property(_get_count)
- hits = property(_get_hits)
- pages = property(_get_pages)
- page_range = property(_get_page_range)
+ # The old API called it "hits" instead of "count".
+ hits = count
+
+ # The old API called it "pages" instead of "num_pages".
+ pages = Paginator.num_pages
View
10 django/core/serializers/base.py
@@ -22,10 +22,10 @@ class Serializer(object):
Abstract serializer base class.
"""
- # Indicates if the implemented serializer is only available for
+ # Indicates if the implemented serializer is only available for
# internal Django use.
internal_use_only = False
-
+
def serialize(self, queryset, **options):
"""
Serialize a queryset.
@@ -60,8 +60,6 @@ def get_string_value(self, obj, field):
"""
if isinstance(field, models.DateTimeField):
value = getattr(obj, field.name).strftime("%Y-%m-%d %H:%M:%S")
- elif isinstance(field, models.FileField):
- value = getattr(obj, "get_%s_url" % field.name, lambda: None)()
else:
value = field.flatten_data(follow=None, obj=obj).get(field.name, "")
return smart_unicode(value)
@@ -162,9 +160,9 @@ def __repr__(self):
return "<DeserializedObject: %s>" % smart_str(self.object)
def save(self, save_m2m=True):
- # Call save on the Model baseclass directly. This bypasses any
+ # Call save on the Model baseclass directly. This bypasses any
# model-defined save. The save is also forced to be raw.
- # This ensures that the data that is deserialized is literally
+ # This ensures that the data that is deserialized is literally
# what came from the file, not post-processed by pre_save/save
# methods.
models.Model.save(self.object, raw=True)
View
14 django/core/servers/fastcgi.py
@@ -37,7 +37,9 @@
maxchildren=NUMBER hard limit number of processes / threads
daemonize=BOOL whether to detach from terminal.
pidfile=FILE write the spawned process-id to this file.
- workdir=DIRECTORY change to this directory when daemonizing
+ workdir=DIRECTORY change to this directory when daemonizing.
+ outlog=FILE write stdout to this file.
+ errlog=FILE write stderr to this file.
Examples:
Run a "standard" fastcgi process on a file-descriptor
@@ -69,6 +71,8 @@
'minspare': 2,
'maxchildren': 50,
'maxrequests': 0,
+ 'outlog': None,
+ 'errlog': None,
}
def fastcgi_help(message=None):
@@ -150,9 +154,15 @@ def runfastcgi(argset=[], **kwargs):
else:
return fastcgi_help("ERROR: Invalid option for daemonize parameter.")
+ daemon_kwargs = {}
+ if options['outlog']:
+ daemon_kwargs['out_log'] = options['outlog']
+ if options['errlog']:
+ daemon_kwargs['err_log'] = options['errlog']
+
if daemonize:
from django.utils.daemonize import become_daemon
- become_daemon(our_home_dir=options["workdir"])
+ become_daemon(our_home_dir=options["workdir"], **daemon_kwargs)
if options["pidfile"]:
fp = open(options["pidfile"], "w")
View
10 django/db/models/fields/__init__.py
@@ -846,6 +846,16 @@ def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=
self.path, self.match, self.recursive = path, match, recursive
kwargs['max_length'] = kwargs.get('max_length', 100)
Field.__init__(self, verbose_name, name, **kwargs)
+
+ def formfield(self, **kwargs):
+ defaults = {
+ 'path': self.path,
+ 'match': self.match,
+ 'recursive': self.recursive,
+ 'form_class': forms.FilePathField,
+ }
+ defaults.update(kwargs)
+ return super(FilePathField, self).formfield(**defaults)
def get_manipulator_field_objs(self):
return [curry(oldforms.FilePathField, path=self.path, match=self.match, recursive=self.recursive)]
View
7 django/db/models/fields/related.py
@@ -548,6 +548,13 @@ def prepare_field_objs_and_params(self, manipulator, name_prefix):
params['choices'] = self.get_choices_default()
return field_objs, params
+ def get_default(self):
+ "Here we check if the default value is an object and return the to_field if so."
+ field_default = super(ForeignKey, self).get_default()
+ if isinstance(field_default, self.rel.to):
+ return getattr(field_default, self.rel.get_related_field().attname)
+ return field_default
+
def get_manipulator_field_objs(self):
rel_field = self.rel.get_related_field()
if self.rel.raw_id_admin and not isinstance(rel_field, AutoField):
View
15 django/db/models/query.py
@@ -1,5 +1,5 @@
from django.conf import settings
-from django.db import connection, transaction
+from django.db import connection, transaction, IntegrityError
from django.db.models.fields import DateField, FieldDoesNotExist
from django.db.models import signals, loading
from django.dispatch import dispatcher
@@ -285,11 +285,14 @@ def get_or_create(self, **kwargs):
try:
return self.get(**kwargs), False
except self.model.DoesNotExist:
- params = dict([(k, v) for k, v in kwargs.items() if '__' not in k])
- params.update(defaults)
- obj = self.model(**params)
- obj.save()
- return obj, True
+ try:
+ params = dict([(k, v) for k, v in kwargs.items() if '__' not in k])
+ params.update(defaults)
+ obj = self.model(**params)
+ obj.save()
+ return obj, True
+ except IntegrityError, e:
+ return self.get(**kwargs), False
def latest(self, field_name=None):
"""
View
3 django/http/__init__.py
@@ -82,6 +82,9 @@ def build_absolute_uri(self, location=None):
def is_secure(self):
return os.environ.get("HTTPS") == "on"
+ def is_ajax(self):
+ return self.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
+
def _set_encoding(self, val):
"""
Sets the encoding used for GET/POST accesses. If the GET or POST
View
28 django/newforms/extras/widgets.py
@@ -3,6 +3,7 @@
"""
import datetime
+import re
from django.newforms.widgets import Widget, Select
from django.utils.dates import MONTHS
@@ -10,6 +11,8 @@
__all__ = ('SelectDateWidget',)
+RE_DATE = re.compile(r'(\d{4})-(\d\d?)-(\d\d?)$')
+
class SelectDateWidget(Widget):
"""
A Widget that splits date input into three <select> boxes.
@@ -32,28 +35,43 @@ def __init__(self, attrs=None, years=None):
def render(self, name, value, attrs=None):
try:
- value = datetime.date(*map(int, value.split('-')))
year_val, month_val, day_val = value.year, value.month, value.day
- except (AttributeError, TypeError, ValueError):
+ except AttributeError:
year_val = month_val = day_val = None
+ if isinstance(value, basestring):
+ match = RE_DATE.match(value)
+ if match:
+ year_val, month_val, day_val = [int(v) for v in match.groups()]
output = []
+ if 'id' in self.attrs:
+ id_ = self.attrs['id']
+ else:
+ id_ = 'id_%s' % name
+
month_choices = MONTHS.items()
month_choices.sort()
- select_html = Select(choices=month_choices).render(self.month_field % name, month_val)
+ local_attrs = self.build_attrs(id=self.month_field % id_)
+ select_html = Select(choices=month_choices).render(self.month_field % name, month_val, local_attrs)
output.append(select_html)
day_choices = [(i, i) for i in range(1, 32)]
- select_html = Select(choices=day_choices).render(self.day_field % name, day_val)
+ local_attrs['id'] = self.day_field % id_
+ select_html = Select(choices=day_choices).render(self.day_field % name, day_val, local_attrs)
output.append(select_html)
year_choices = [(i, i) for i in self.years]
- select_html = Select(choices=year_choices).render(self.year_field % name, year_val)
+ local_attrs['id'] = self.year_field % id_
+ select_html = Select(choices=year_choices).render(self.year_field % name, year_val, local_attrs)
output.append(select_html)
return mark_safe(u'\n'.join(output))
+ def id_for_label(self, id_):
+ return '%s_month' % id_
+ id_for_label = classmethod(id_for_label)
+
def value_from_datadict(self, data, files, name):
y, m, d = data.get(self.year_field % name), data.get(self.month_field % name), data.get(self.day_field % name)
if y and m and d:
View
30 django/newforms/fields.py
@@ -4,6 +4,7 @@
import copy
import datetime
+import os
import re
import time
# Python 2.3 fallbacks
@@ -31,7 +32,7 @@
'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
- 'SplitDateTimeField', 'IPAddressField',
+ 'SplitDateTimeField', 'IPAddressField', 'FilePathField',
)
# These values, if given to to_python(), will trigger the self.required check.
@@ -718,6 +719,33 @@ def compress(self, data_list):
"""
raise NotImplementedError('Subclasses must implement this method.')
+class FilePathField(ChoiceField):
+ def __init__(self, path, match=None, recursive=False, required=True,
+ widget=Select, label=None, initial=None, help_text=None,
+ *args, **kwargs):
+ self.path, self.match, self.recursive = path, match, recursive
+ super(FilePathField, self).__init__(choices=(), required=required,
+ widget=widget, label=label, initial=initial, help_text=help_text,
+ *args, **kwargs)
+ self.choices = []
+ if self.match is not None:
+ self.match_re = re.compile(self.match)
+ if recursive:
+ for root, dirs, files in os.walk(self.path):
+ for f in files:
+ if self.match is None or self.match_re.search(f):
+ f = os.path.join(root, f)
+ self.choices.append((f, f.replace(path, "", 1)))
+ else:
+ try:
+ for f in os.listdir(self.path):
+ full_file = os.path.join(self.path, f)
+ if os.path.isfile(full_file) and (self.match is None or self.match_re.search(f)):
+ self.choices.append((full_file, f))
+ except OSError:
+ pass
+ self.widget.choices = self.choices
+
class SplitDateTimeField(MultiValueField):
default_error_messages = {
'invalid_date': _(u'Enter a valid date.'),
View
43 django/newforms/models.py
@@ -277,19 +277,18 @@ class ModelForm(BaseModelForm):
# Fields #####################################################################
-class QuerySetIterator(object):
- def __init__(self, queryset, empty_label, cache_choices):
- self.queryset = queryset
- self.empty_label = empty_label
- self.cache_choices = cache_choices
+class ModelChoiceIterator(object):
+ def __init__(self, field):
+ self.field = field
+ self.queryset = field.queryset
def __iter__(self):
- if self.empty_label is not None:
- yield (u"", self.empty_label)
+ if self.field.empty_label is not None:
+ yield (u"", self.field.empty_label)
for obj in self.queryset:
- yield (obj.pk, smart_unicode(obj))
+ yield (obj.pk, self.field.label_from_instance(obj))
# Clear the QuerySet cache if required.
- if not self.cache_choices:
+ if not self.field.cache_choices:
self.queryset._result_cache = None
class ModelChoiceField(ChoiceField):
@@ -306,6 +305,7 @@ def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
help_text=None, *args, **kwargs):
self.empty_label = empty_label
self.cache_choices = cache_choices
+
# Call Field instead of ChoiceField __init__() because we don't need
# ChoiceField.__init__().
Field.__init__(self, required, widget, label, initial, help_text,
@@ -321,19 +321,30 @@ def _set_queryset(self, queryset):
queryset = property(_get_queryset, _set_queryset)
+ # this method will be used to create object labels by the QuerySetIterator.
+ # Override it to customize the label.
+ def label_from_instance(self, obj):
+ """
+ This method is used to convert objects into strings; it's used to
+ generate the labels for the choices presented by this object. Subclasses
+ can override this method to customize the display of the choices.
+ """
+ return smart_unicode(obj)
+
def _get_choices(self):
# If self._choices is set, then somebody must have manually set
# the property self.choices. In this case, just return self._choices.
if hasattr(self, '_choices'):
return self._choices
+
# Otherwise, execute the QuerySet in self.queryset to determine the
- # choices dynamically. Return a fresh QuerySetIterator that has not
- # been consumed. Note that we're instantiating a new QuerySetIterator
- # *each* time _get_choices() is called (and, thus, each time
- # self.choices is accessed) so that we can ensure the QuerySet has not
- # been consumed.
- return QuerySetIterator(self.queryset, self.empty_label,
- self.cache_choices)
+ # choices dynamically. Return a fresh QuerySetIterator that has not been
+ # consumed. Note that we're instantiating a new QuerySetIterator *each*
+ # time _get_choices() is called (and, thus, each time self.choices is
+ # accessed) so that we can ensure the QuerySet has not been consumed. This
+ # construct might look complicated but it allows for lazy evaluation of
+ # the queryset.
+ return ModelChoiceIterator(self)
def _set_choices(self, value):
# This method is copied from ChoiceField._set_choices(). It's necessary
View
5 django/template/defaultfilters.py
@@ -124,7 +124,10 @@ def floatformat(text, arg=-1):
d = int(arg)
except ValueError:
return force_unicode(f)
- m = f - int(f)
+ try:
+ m = f - int(f)
+ except OverflowError:
+ return force_unicode(f)
if not m and d < 0:
return mark_safe(u'%d' % int(f))
else:
View
5 django/test/client.py
@@ -1,3 +1,4 @@
+import urllib
import sys
from cStringIO import StringIO
from django.conf import settings
@@ -208,7 +209,7 @@ def get(self, path, data={}, **extra):
r = {
'CONTENT_LENGTH': None,
'CONTENT_TYPE': 'text/html; charset=utf-8',
- 'PATH_INFO': path,
+ 'PATH_INFO': urllib.unquote(path),
'QUERY_STRING': urlencode(data, doseq=True),
'REQUEST_METHOD': 'GET',
}
@@ -227,7 +228,7 @@ def post(self, path, data={}, content_type=MULTIPART_CONTENT, **extra):
r = {
'CONTENT_LENGTH': len(post_data),
'CONTENT_TYPE': content_type,
- 'PATH_INFO': path,
+ 'PATH_INFO': urllib.unquote(path),
'REQUEST_METHOD': 'POST',
'wsgi.input': StringIO(post_data),
}
View
6 django/utils/daemonize.py
@@ -18,10 +18,10 @@ def become_daemon(our_home_dir='.', out_log='/dev/null', err_log='/dev/null'):
# Second fork
try:
if os.fork() > 0:
- sys.exit(0)
+ os._exit(0)
except OSError, e:
sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror))
- sys.exit(1)
+ os._exit(1)
si = open('/dev/null', 'r')
so = open(out_log, 'a+', 0)
@@ -29,6 +29,8 @@ def become_daemon(our_home_dir='.', out_log='/dev/null', err_log='/dev/null'):
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
+ # Set custom file descriptors so that they get proper buffering.
+ sys.stdout, sys.stderr = so, se
else:
def become_daemon(our_home_dir='.', out_log=None, err_log=None):
"""
View
17 django/views/debug.py
@@ -1,11 +1,12 @@
import os
import re
import sys
+import datetime
from django.conf import settings
from django.template import Template, Context, TemplateDoesNotExist
from django.utils.html import escape
-from django.http import HttpResponseServerError, HttpResponseNotFound
+from django.http import HttpResponse, HttpResponseServerError, HttpResponseNotFound
from django.utils.encoding import smart_unicode
HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD|PROFANITIES_LIST')
@@ -70,6 +71,11 @@ def technical_500_response(request, exc_type, exc_value, tb):
Create a technical server error response. The last three arguments are
the values returned from sys.exc_info() and friends.
"""
+ html = get_traceback_html(request, exc_type, exc_value, tb)
+ return HttpResponseServerError(html, mimetype='text/html')
+
+def get_traceback_html(request, exc_type, exc_value, tb):
+ "Return HTML code for traceback."
template_info = None
template_does_not_exist = False
loader_debug_info = None
@@ -153,13 +159,14 @@ def technical_500_response(request, exc_type, exc_value, tb):
'settings': get_safe_settings(),
'sys_executable': sys.executable,
'sys_version_info': '%d.%d.%d' % sys.version_info[0:3],
+ 'server_time': datetime.datetime.now(),
'django_version_info': get_version(),
'sys_path' : sys.path,
'template_info': template_info,
'template_does_not_exist': template_does_not_exist,
'loader_debug_info': loader_debug_info,
})
- return HttpResponseServerError(t.render(c), mimetype='text/html')
+ return t.render(c)
def technical_404_response(request, exception):
"Create a technical 404 error response. The exception should be the Http404."
@@ -190,7 +197,7 @@ def empty_urlconf(request):
c = Context({
'project_name': settings.SETTINGS_MODULE.split('.')[0]
})
- return HttpResponseNotFound(t.render(c), mimetype='text/html')
+ return HttpResponse(t.render(c), mimetype='text/html')
def _get_lines_from_file(filename, lineno, context_lines, loader=None, module_name=None):
"""
@@ -384,6 +391,10 @@ def _get_lines_from_file(filename, lineno, context_lines, loader=None, module_na
<th>Python Path:</th>
<td>{{ sys_path }}</td>
</tr>
+ <tr>
+ <th>Server time:</th>
+ <td>{{server_time|date:"r"}}</td>
+ </tr>
</table>
</div>
{% if unicode_hint %}
View
50 django/views/generic/list_detail.py
@@ -1,7 +1,7 @@
from django.template import loader, RequestContext
from django.http import Http404, HttpResponse
from django.core.xheaders import populate_xheaders
-from django.core.paginator import ObjectPaginator, InvalidPage
+from django.core.paginator import QuerySetPaginator, InvalidPage
from django.core.exceptions import ObjectDoesNotExist
def object_list(request, queryset, paginate_by=None, page=None,
@@ -45,43 +45,47 @@ def object_list(request, queryset, paginate_by=None, page=None,
if extra_context is None: extra_context = {}
queryset = queryset._clone()
if paginate_by:
- paginator = ObjectPaginator(queryset, paginate_by)
+ paginator = QuerySetPaginator(queryset, paginate_by, allow_empty_first_page=allow_empty)
if not page:
page = request.GET.get('page', 1)
try:
page_number = int(page)
except ValueError:
if page == 'last':
- page_number = paginator.pages
+ page_number = paginator.num_pages
else:
- # Page is not 'last', nor can it be converted to an int
+ # Page is not 'last', nor can it be converted to an int.
raise Http404
try:
- object_list = paginator.get_page(page_number - 1)
+ page_obj = paginator.page(page_number)
except InvalidPage:
- if page_number == 1 and allow_empty:
- object_list = []
- else:
- raise Http404
+ raise Http404
c = RequestContext(request, {
- '%s_list' % template_object_name: object_list,
- 'is_paginated': paginator.pages > 1,
- 'results_per_page': paginate_by,
- 'has_next': paginator.has_next_page(page_number - 1),
- 'has_previous': paginator.has_previous_page(page_number - 1),
- 'page': page_number,
- 'next': page_number + 1,
- 'previous': page_number - 1,
- 'last_on_page': paginator.last_on_page(page_number - 1),
- 'first_on_page': paginator.first_on_page(page_number - 1),
- 'pages': paginator.pages,
- 'hits' : paginator.hits,
- 'page_range' : paginator.page_range
+ '%s_list' % template_object_name: page_obj.object_list,
+ 'paginator': paginator,
+ 'page_obj': page_obj,
+
+ # Legacy template context stuff. New templates should use page_obj
+ # to access this instead.
+ 'is_paginated': page_obj.has_other_pages(),
+ 'results_per_page': paginator.per_page,
+ 'has_next': page_obj.has_next(),
+ 'has_previous': page_obj.has_previous(),
+ 'page': page_obj.number,
+ 'next': page_obj.next_page_number(),
+ 'previous': page_obj.previous_page_number(),
+ 'first_on_page': page_obj.start_index(),
+ 'last_on_page': page_obj.end_index(),
+ 'pages': paginator.num_pages,
+ 'hits': paginator.count,
+ 'page_range': paginator.page_range,
}, context_processors)
else:
c = RequestContext(request, {
'%s_list' % template_object_name: queryset,
- 'is_paginated': False
+ 'paginator': None,
+ 'page_obj': None,
+ 'is_paginated': False,
}, context_processors)
if not allow_empty and len(queryset) == 0:
raise Http404
View
4 docs/custom_model_fields.txt
@@ -401,8 +401,8 @@ For example::
# ...
def get_db_prep_save(self, value):
- return ''.join([''.join(l) for l in (self.north,
- self.east, self.south, self.west)])
+ return ''.join([''.join(l) for l in (value.north,
+ value.east, value.south, value.west)])
``pre_save(self, model_instance, add)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
View
4 docs/db-api.txt
@@ -1372,7 +1372,7 @@ equivalent::
Entry.objects.filter(blog__pk=3) # __pk implies __id__exact
Lookups that span relationships
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-------------------------------
Django offers a powerful and intuitive way to "follow" relationships in
lookups, taking care of the SQL ``JOIN``\s for you automatically, behind the
@@ -1396,7 +1396,7 @@ whose ``headline`` contains ``'Lennon'``::
Blog.objects.filter(entry__headline__contains='Lennon')
Escaping percent signs and underscores in LIKE statements
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+---------------------------------------------------------
The field lookups that equate to ``LIKE`` SQL statements (``iexact``,
``contains``, ``icontains``, ``startswith``, ``istartswith``, ``endswith``
View
17 docs/generic_views.txt
@@ -751,6 +751,19 @@ In addition to ``extra_context``, the template's context will be:
If the results are paginated, the context will contain these extra variables:
+ * **New in Django development version:** ``paginator``: An instance of
+ ``django.core.paginator.Paginator``.
+
+ * **New in Django development version:** ``page_obj``: An instance of
+ ``django.core.paginator.Page``.
+
+In older versions of Django, before ``paginator`` and ``page_obj`` were added
+to this template's context, the template included several other variables
+related to pagination. Note that you should *NOT* use these variables anymore;
+use ``paginator`` and ``page_obj`` instead, because they let you do everything
+these old variables let you do (and more!). But for legacy installations,
+here's a list of those old template variables:
+
* ``results_per_page``: The number of objects per page. (Same as the
``paginate_by`` parameter.)
@@ -777,8 +790,8 @@ If the results are paginated, the context will contain these extra variables:
* ``hits``: The total number of objects across *all* pages, not just this
page.
- * **New in Django development version:** ``page_range``: A list of the
- page numbers that are available. This is 1-based.
+ * ``page_range``: A list of the page numbers that are available. This is
+ 1-based.
Notes on pagination
~~~~~~~~~~~~~~~~~~~
View
14 docs/install.txt
@@ -167,6 +167,20 @@ These commands will install Django in your Python installation's
Installing the development version
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. admonition:: Tracking Django development
+
+ If you decide to use the latest development version of Django,
+ you'll want to pay close attention to `the development timeline`_,
+ and you'll want to keep an eye on `the list of
+ backwards-incompatible changes`_; this will help you stay on top
+ of any new features you might want to use, as well as any changes
+ you'll need to make to your code when updating your copy of Django
+ (for stable releases, any necessary changes are documented in the
+ release notes).
+
+.. _the development timeline: http://code.djangoproject.com/timeline
+.. _the list of backwards-incompatible changes: http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges
+
If you'd like to be able to update your Django code occasionally with the
latest bug fixes and improvements, follow these instructions:
View
20 docs/model-api.txt
@@ -626,7 +626,8 @@ option is ignored.
``default``
~~~~~~~~~~~
-The default value for the field.
+The default value for the field. This can be a value or a callable object. If