Commits

Anonymous committed a0b4e59

[multi-db] Merged trunk to [4050]. Some tests still failing.

Comments (0)

Files changed (34)

django/conf/global_settings.py

 DATABASE_PASSWORD = ''         # Not used with sqlite3.
 DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3.
 DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
+DATABASE_OPTIONS = {}          # Set to empy dictionary for default.
 
 # Optional named database connections in addition to the default.
 OTHER_DATABASES = {}
 # Hint: you really don't!
 TRANSACTIONS_MANAGED = False
 
+# The User-Agent string to use when checking for URL validity through the
+# isExistingURL validator.
+URL_VALIDATOR_USER_AGENT = "Django/0.96pre (http://www.djangoproject.com)"
+
 ##############
 # MIDDLEWARE #
 ##############

django/contrib/admin/media/js/dateparse.js

         handler: function(bits) {
             var d = new Date();
             d.setYear(parseInt(bits[1]));
+            d.setMonth(parseInt(bits[2], 10) - 1);
             d.setDate(parseInt(bits[3], 10));
-            d.setMonth(parseInt(bits[2], 10) - 1);
             return d;
         }
     },

django/contrib/admin/templatetags/admin_modify.py

 output_all = register.simple_tag(output_all)
 
 def auto_populated_field_script(auto_pop_fields, change = False):
+    t = []
     for field in auto_pop_fields:
-        t = []
         if change:
             t.append('document.getElementById("id_%s")._changed = true;' % field.name)
         else:

django/contrib/comments/models.py

         """
         Given a rating_string, this returns a tuple of (rating_range, options).
         >>> s = "scale:1-10|First_category|Second_category"
-        >>> get_rating_options(s)
+        >>> Comment.objects.get_rating_options(s)
         ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ['First category', 'Second category'])
         """
         rating_range, options = rating_string.split('|', 1)

django/core/management.py

 get_sql_reset.help_doc = "Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given app name(s)."
 get_sql_reset.args = APP_ARGS
 
+def get_sql_initial_data_for_model(model):
+    from django.db import models
+    from django.conf import settings
+
+    opts = model._meta
+    app_dir = os.path.normpath(os.path.join(os.path.dirname(models.get_app(model._meta.app_label).__file__), 'sql'))
+    output = []
+
+    # Some backends can't execute more than one SQL statement at a time,
+    # so split into separate statements.
+    statements = re.compile(r";[ \t]*$", re.M)
+
+    # Find custom SQL, if it's available.
+    sql_files = [os.path.join(app_dir, "%s.%s.sql" % (opts.object_name.lower(), settings.DATABASE_ENGINE)),
+                 os.path.join(app_dir, "%s.sql" % opts.object_name.lower())]
+    for sql_file in sql_files:
+        if os.path.exists(sql_file):
+            fp = open(sql_file, 'U')
+            for statement in statements.split(fp.read()):
+                # Remove any comments from the file
+                statement = re.sub(r"--.*[\n\Z]", "", statement)
+                if statement.strip():
+                    output.append(statement + ";")
+            fp.close()
+
+    return output
+
 def get_sql_initial_data(app):
     "Returns a list of the initial INSERT SQL statements for the given app."
     from django.db import model_connection_name

django/core/paginator.py

-from math import ceil
-
 class InvalidPage(Exception):
     pass
 
 class ObjectPaginator(object):
     """
-    This class makes pagination easy. Feed it a QuerySet, plus the number of
-    objects you want on each page. Then read the hits and pages properties to
+    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
     """
-    def __init__(self, query_set, num_per_page):
+    def __init__(self, query_set, num_per_page, orphans=0):
         self.query_set = query_set
         self.num_per_page = num_per_page
-        self._hits, self._pages = None, None
-        self._has_next = {} # Caches page_number -> has_next_boolean
+        self.orphans = orphans
+        self._hits = self._pages = None
 
-    def get_page(self, page_number):
+    def validate_page_number(self, page_number):
         try:
             page_number = int(page_number)
         except ValueError:
             raise InvalidPage
-        if page_number < 0:
+        if page_number < 0 or page_number > self.pages - 1:
             raise InvalidPage
+        return page_number
 
-        # Retrieve one extra record, and check for the existence of that extra
-        # record to determine whether there's a next page.
-        limit = self.num_per_page + 1
-        offset = page_number * self.num_per_page
-
-        object_list = list(self.query_set[offset:offset+limit])
-
-        if not object_list:
-            raise InvalidPage
-
-        self._has_next[page_number] = (len(object_list) > self.num_per_page)
-        return object_list[:self.num_per_page]
+    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]
 
     def has_next_page(self, page_number):
         "Does page $page_number have a 'next' page?"
-        if not self._has_next.has_key(page_number):
-            if self._pages is None:
-                offset = (page_number + 1) * self.num_per_page
-                self._has_next[page_number] = len(self.query_set[offset:offset+1]) > 0
-            else:
-                self._has_next[page_number] = page_number < (self.pages - 1)
-        return self._has_next[page_number]
+        return page_number < self.pages - 1
 
     def has_previous_page(self, page_number):
         return page_number > 0
         Returns the 1-based index of the first object on the given page,
         relative to total objects found (hits).
         """
-        if page_number == 0:
-            return 1
+        page_number = self.validate_page_number(page_number)
         return (self.num_per_page * page_number) + 1
 
     def last_on_page(self, page_number):
         Returns the 1-based index of the last object on the given page,
         relative to total objects found (hits).
         """
-        if page_number == 0 and self.num_per_page >= self._hits:
-            return self._hits
-        elif page_number == (self._pages - 1) and (page_number + 1) * self.num_per_page > self._hits:
-            return self._hits
-        return (page_number + 1) * self.num_per_page
+        page_number = self.validate_page_number(page_number)
+        page_number += 1   # 1-base
+        if page_number == self.pages:
+            return self.hits
+        return page_number * self.num_per_page
 
     def _get_hits(self):
         if self._hits is None:
-            self._hits = self.query_set.count()
+            # Try .count() or fall back to len().
+            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:
-            self._pages = int(ceil(self.hits / float(self.num_per_page)))
+            hits = (self.hits - 1 - self.orphans)
+            if hits < 1:
+                hits = 0
+            self._pages = hits // self.num_per_page + 1
         return self._pages
 
     hits = property(_get_hits)

django/core/serializers/base.py

         self.options = options
 
         self.stream = options.get("stream", StringIO())
+        self.selected_fields = options.get("fields")
 
         self.start_serialization()
         for obj in queryset:
                 if field is obj._meta.pk:
                     continue
                 elif field.rel is None:
-                    self.handle_field(obj, field)
+                    if self.selected_fields is None or field.attname in self.selected_fields:
+                        self.handle_field(obj, field)
                 else:
-                    self.handle_fk_field(obj, field)
+                    if self.selected_fields is None or field.attname[:-3] in self.selected_fields:
+                        self.handle_fk_field(obj, field)
             for field in obj._meta.many_to_many:
-                self.handle_m2m_field(obj, field)
+                if self.selected_fields is None or field.attname in self.selected_fields:
+                    self.handle_m2m_field(obj, field)
             self.end_object(obj)
         self.end_serialization()
         return self.getvalue()

django/core/serializers/python.py

                 m2m_data[field.name] = field.rel.to._default_manager.in_bulk(field_value).values()
                 
             # Handle FK fields
-            elif field.rel and isinstance(field.rel, models.ManyToOneRel):
+            elif field.rel and isinstance(field.rel, models.ManyToOneRel) and field_value is not None:
                 try:
                     data[field.name] = field.rel.to._default_manager.get(pk=field_value)
                 except field.rel.to.DoesNotExist:

django/core/serializers/xml_serializer.py

         # If it doesn't exist, set the field to None (which might trigger 
         # validation error, but that's expected).
         RelatedModel = self._get_model_from_node(node, "to")
-        return RelatedModel.objects.get(pk=getInnerText(node).strip().encode(self.encoding))
+        # Check if there is a child node named 'None', returning None if so.
+        if len(node.childNodes) == 1 and node.childNodes[0].nodeName == 'None':
+            return None
+        else:
+            return RelatedModel.objects.get(pk=getInnerText(node).strip().encode(self.encoding))
         
     def _handle_m2m_field_node(self, node):
         """

django/core/servers/fastcgi.py

   method=IMPL          prefork or threaded (default prefork)
   maxrequests=NUMBER   number of requests a child handles before it is 
                        killed and a new child is forked (0 = no limit).
-  maxspare=NUMBER      max number of spare processes to keep running.
-  minspare=NUMBER      min number of spare processes to prefork.
-  maxchildren=NUMBER   hard limit number of processes in prefork mode.
+  maxspare=NUMBER      max number of spare processes / threads
+  minspare=NUMBER      min number of spare processes / threads.
+  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
         }
     elif options['method'] in ('thread', 'threaded'):
         from flup.server.fcgi import WSGIServer
-        wsgi_opts = {}
+        wsgi_opts = {
+            'maxSpare': int(options["maxspare"]),
+            'minSpare': int(options["minspare"]),
+            'maxThreads': int(options["maxchildren"]),
+        }
     else:
         return fastcgi_help("ERROR: Implementation must be one of prefork or thread.")
 

django/core/urlresolvers.py

 def get_mod_func(callback):
     # Converts 'django.views.news.stories.story_detail' to
     # ['django.views.news.stories', 'story_detail']
-    dot = callback.rindex('.')
+    try:
+        dot = callback.rindex('.')
+    except ValueError:
+        return callback, ''
     return callback[:dot], callback[dot+1:]
 
 def reverse_helper(regex, *args, **kwargs):

django/core/validators.py

 form field is required.
 """
 
+import urllib2
 from django.conf import settings
 from django.utils.translation import gettext, gettext_lazy, ngettext
 from django.utils.functional import Promise, lazy
     isWellFormedXml('<root>%s</root>' % field_data, all_data)
 
 def isExistingURL(field_data, all_data):
-    import urllib2
     try:
-        u = urllib2.urlopen(field_data)
+        headers = {
+            "Accept" : "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
+            "Accept-Language" : "en-us,en;q=0.5",
+            "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
+            "Connection" : "close",
+            "User-Agent": settings.URL_VALIDATOR_USER_AGENT
+            }
+        req = urllib2.Request(field_data,None, headers)
+        u = urllib2.urlopen(req)
     except ValueError:
-        raise ValidationError, gettext("Invalid URL: %s") % field_data
+        raise ValidationError, _("Invalid URL: %s") % field_data
     except urllib2.HTTPError, e:
         # 401s are valid; they just mean authorization is required.
-        if e.code not in ('401',):
-            raise ValidationError, gettext("The URL %s is a broken link.") % field_data
+        # 301 and 302 are redirects; they just mean look somewhere else.
+        if str(e.code) not in ('401','301','302'):
+            raise ValidationError, _("The URL %s is a broken link.") % field_data
     except: # urllib2.URLError, httplib.InvalidURL, etc.
-        raise ValidationError, gettext("The URL %s is a broken link.") % field_data
-
+        raise ValidationError, _("The URL %s is a broken link.") % field_data
+        
 def isValidUSState(field_data, all_data):
     "Checks that the given string is a valid two-letter U.S. state abbreviation"
     states = ['AA', 'AE', 'AK', 'AL', 'AP', 'AR', 'AS', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL', 'FM', 'GA', 'GU', 'HI', 'IA', 'ID', 'IL', 'IN', 'KS', 'KY', 'LA', 'MA', 'MD', 'ME', 'MH', 'MI', 'MN', 'MO', 'MP', 'MS', 'MT', 'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY', 'OH', 'OK', 'OR', 'PA', 'PR', 'PW', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VA', 'VI', 'VT', 'WA', 'WI', 'WV', 'WY']
             if field_name != self.field_name and value == field_data:
                 raise ValidationError, self.error_message
 
+class NumberIsInRange(object):
+    """
+    Validator that tests if a value is in a range (inclusive).
+    """
+    def __init__(self, lower=None, upper=None, error_message=''):
+        self.lower, self.upper = lower, upper
+        if not error_message:
+            if lower and upper:
+                self.error_message = gettext("This value must be between %s and %s.") % (lower, upper)
+            elif lower:
+                self.error_message = gettext("This value must be at least %s.") % lower
+            elif upper:
+                self.error_message = gettext("This value must be no more than %s.") % upper
+        else:
+            self.error_message = error_message
+
+    def __call__(self, field_data, all_data):
+        # Try to make the value numeric. If this fails, we assume another 
+        # validator will catch the problem.
+        try:
+            val = float(field_data)
+        except ValueError:
+            return
+            
+        # Now validate
+        if self.lower and self.upper and (val < self.lower or val > self.upper):
+            raise ValidationError(self.error_message)
+        elif self.lower and val < self.lower:
+            raise ValidationError(self.error_message)
+        elif self.upper and val > self.upper:
+            raise ValidationError(self.error_message)
+
 class IsAPowerOf(object):
     """
     >>> v = IsAPowerOf(2)

django/db/__init__.py

 
 if not settings.DATABASE_ENGINE:
     settings.DATABASE_ENGINE = 'dummy'
-
+    
 
 def connect(settings, **kw):
     """Connect to the database specified in settings. Returns a
         super(ConnectionInfo, self).__init__(**kw)
         if settings is None:
             from django.conf import settings
+        if not settings.DATABASE_OPTIONS:
+            settings.DATABASE_OPTIONS = {}
         self.settings = settings
         self.backend = self.load_backend()
         self.connection = self.backend.DatabaseWrapper(settings)

django/db/backends/ado_mssql/base.py

 class DatabaseWrapper(object):
     def __init__(self, settings):
         self.settings = settings
+        self.options = settings.DATABASE_OPTIONS
         self.connection = None
         self.queries = []
 

django/db/backends/ansi/sql.py

                     output.append(BoundStatement(fp.read(), db.connection))
                 else:                                 
                     for statement in statements.split(fp.read()):
+                        # Remove any comments from the file
+                        statement = re.sub(r"--.*[\n\Z]", "", statement)
                         if statement.strip():
                             output.append(BoundStatement(statement + ";",
                                                          db.connection))

django/db/backends/mysql/base.py

         self.connection = None
         self.queries = []
         self.server_version = None
+        self.options = settings.DATABASE_OPTIONS
 
     def _valid_connection(self):
         if self.connection is not None:
                 kwargs['host'] = settings.DATABASE_HOST
             if settings.DATABASE_PORT:
                 kwargs['port'] = int(settings.DATABASE_PORT)
+            kwargs.update(self.options)
             self.connection = Database.connect(**kwargs)
         cursor = self.connection.cursor()
         if self.connection.get_server_info() >= '4.1':

django/db/backends/oracle/base.py

         self.settings = settings
         self.connection = None
         self.queries = []
+        self.options = settings.DATABASE_OPTIONS
 
     def _valid_connection(self):
         return self.connection is not None
                 settings.DATABASE_HOST = 'localhost'
             if len(settings.DATABASE_PORT.strip()) != 0:
                 dsn = Database.makedsn(settings.DATABASE_HOST, int(settings.DATABASE_PORT), settings.DATABASE_NAME)
-                self.connection = Database.connect(settings.DATABASE_USER, settings.DATABASE_PASSWORD, dsn)
+                self.connection = Database.connect(settings.DATABASE_USER, settings.DATABASE_PASSWORD, dsn, **self.options)
             else:
                 conn_string = "%s/%s@%s" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME)
-                self.connection = Database.connect(conn_string)
+                self.connection = Database.connect(conn_string, **self.options)
         return FormatStylePlaceholderCursor(self.connection)
 
     def _commit(self):

django/db/backends/postgresql/base.py

         self.settings = settings
         self.connection = None
         self.queries = []
+        self.options = settings.DATABASE_OPTIONS
 
     def cursor(self):
         settings = self.settings
                 conn_string += " host=%s" % settings.DATABASE_HOST
             if settings.DATABASE_PORT:
                 conn_string += " port=%s" % settings.DATABASE_PORT
-            self.connection = Database.connect(conn_string)
+            self.connection = Database.connect(conn_string, **self.options)
             self.connection.set_isolation_level(1) # make transactions transparent to all cursors
         cursor = self.connection.cursor()
         cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE])

django/db/backends/postgresql_psycopg2/base.py

         self.settings = settings
         self.connection = None
         self.queries = []
+        self.options = settings.DATABASE_OPTIONS
 
     def cursor(self):
         settings = self.settings
                 conn_string += " host=%s" % settings.DATABASE_HOST
             if settings.DATABASE_PORT:
                 conn_string += " port=%s" % settings.DATABASE_PORT
-            self.connection = Database.connect(conn_string)
+            self.connection = Database.connect(conn_string, **self.options)
             self.connection.set_isolation_level(1) # make transactions transparent to all cursors
         cursor = self.connection.cursor()
         cursor.tzinfo_factory = None

django/db/backends/sqlite3/base.py

         self.settings = settings
         self.connection = None
         self.queries = []
+        self.options = settings.DATABASE_OPTIONS
 
     def cursor(self):
         settings = self.settings
         if self.connection is None:
-            self.connection = Database.connect(settings.DATABASE_NAME,
-                detect_types=Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES)
-
+            kwargs = {
+                'database': settings.DATABASE_NAME,
+                'detect_types': Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES,
+            }
+            kwargs.update(self.options)
+            self.connection = Database.connect(**kwargs)
             # Register extract and date_trunc functions.
             self.connection.create_function("django_extract", 2, _sqlite_extract)
             self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc)

django/db/backends/util.py

             if not isinstance(params, (tuple, dict)):
                 params = tuple(params)
             self.db.queries.append({
-                'sql': sql % tuple(params),
+                'sql': sql % params,
                 'time': "%.3f" % (stop - start),
             })
 

django/db/models/fields/__init__.py

         # If the raw path is passed in, validate it's under the MEDIA_ROOT.
         def isWithinMediaRoot(field_data, all_data):
             f = os.path.abspath(os.path.join(settings.MEDIA_ROOT, field_data))
-            if not f.startswith(os.path.normpath(settings.MEDIA_ROOT)):
+            if not f.startswith(os.path.abspath(os.path.normpath(settings.MEDIA_ROOT))):
                 raise validators.ValidationError, _("Enter a valid filename.")
         field_list[1].validator_list.append(isWithinMediaRoot)
         return field_list

django/db/models/manipulators.py

         # This is really not going to work for fields that have different
         # form fields, e.g. DateTime.
         # This validation needs to occur after html2python to be effective.
-        field_val = all_data.get(f.attname, None)
+        field_val = all_data.get(f.name, None)
         if field_val is None:
             # This will be caught by another validator, assuming the field
             # doesn't have blank=True.

django/forms/__init__.py

     This allows dictionary-style lookups of formfields. It also handles feeding
     prepopulated data and validation error messages to the formfield objects.
     """
-    def __init__(self, manipulator, data, error_dict, edit_inline=True):
-        self.manipulator, self.data = manipulator, data
+    def __init__(self, manipulator, data=None, error_dict=None, edit_inline=True):
+        self.manipulator = manipulator
+        if data is None:
+            data = {}
+        if error_dict is None:
+            error_dict = {}
+        self.data = data
         self.error_dict = error_dict
         self._inline_collections = None
         self.edit_inline = edit_inline

django/newforms/fields.py

     r'(?::\d+)?' # optional port
     r'(?:/?|/\S+)$', re.IGNORECASE)
 
+try:
+    from django.conf import settings
+    URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT
+except ImportError:
+    # It's OK if Django settings aren't configured.
+    URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
+
 class URLField(RegexField):
-    def __init__(self, required=True, verify_exists=False, widget=None):
+    def __init__(self, required=True, verify_exists=False, widget=None,
+            validator_user_agent=URL_VALIDATOR_USER_AGENT):
         RegexField.__init__(self, url_re, u'Enter a valid URL.', required, widget)
         self.verify_exists = verify_exists
+        self.user_agent = validator_user_agent
 
     def clean(self, value):
         value = RegexField.clean(self, value)
         if self.verify_exists:
             import urllib2
+            from django.conf import settings
+            headers = {
+                "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
+                "Accept-Language": "en-us,en;q=0.5",
+                "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
+                "Connection": "close",
+                "User-Agent": self.user_agent,
+            }
             try:
-                u = urllib2.urlopen(value)
+                req = urllib2.Request(field_data, None, headers)
+                u = urllib2.urlopen(req)
             except ValueError:
                 raise ValidationError(u'Enter a valid URL.')
             except: # urllib2.URLError, httplib.InvalidURL, etc.

django/template/__init__.py

                     dict = func(*args)
 
                     if not getattr(self, 'nodelist', False):
-                        from django.template.loader import get_template
-                        t = get_template(file_name)
+                        from django.template.loader import get_template, select_template
+                        if hasattr(file_name, '__iter__'):
+                            t = select_template(file_name)
+                        else:
+                            t = get_template(file_name)
                         self.nodelist = t.nodelist
                     return self.nodelist.render(context_class(dict))
 

django/template/defaultfilters.py

     Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102
     bytes, etc).
     """
-    bytes = float(bytes)
+    try:
+        bytes = float(bytes)
+    except TypeError:
+        return "0 bytes"
+        
     if bytes < 1024:
         return "%d byte%s" % (bytes, bytes != 1 and 's' or '')
     if bytes < 1024 * 1024:

django/template/defaulttags.py

         return nodelist.render(context)
 
 class IfChangedNode(Node):
-    def __init__(self, nodelist):
+    def __init__(self, nodelist, *varlist):
         self.nodelist = nodelist
         self._last_seen = None
+        self._varlist = varlist
 
     def render(self, context):
         if context.has_key('forloop') and context['forloop']['first']:
             self._last_seen = None
-        content = self.nodelist.render(context)
-        if content != self._last_seen:
+        try:
+            if self._varlist:
+                # Consider multiple parameters.
+                # This automatically behaves like a OR evaluation of the multiple variables.
+                compare_to = [resolve_variable(var, context) for var in self._varlist]
+            else:
+                compare_to = self.nodelist.render(context)
+        except VariableDoesNotExist:
+            compare_to = None        
+
+        if  compare_to != self._last_seen:
             firstloop = (self._last_seen == None)
-            self._last_seen = content
+            self._last_seen = compare_to
             context.push()
             context['ifchanged'] = {'firstloop': firstloop}
             content = self.nodelist.render(context)
     """
     Check if a value has changed from the last iteration of a loop.
 
-    The 'ifchanged' block tag is used within a loop. It checks its own rendered
-    contents against its previous state and only displays its content if the
-    value has changed::
+    The 'ifchanged' block tag is used within a loop. It has two possible uses.
 
-        <h1>Archive for {{ year }}</h1>
+    1. Checks its own rendered contents against its previous state and only
+       displays the content if it has changed. For example, this displays a list of
+       days, only displaying the month if it changes::
 
-        {% for date in days %}
-        {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
-        <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
-        {% endfor %}
+            <h1>Archive for {{ year }}</h1>
+
+            {% for date in days %}
+                {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
+                <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
+            {% endfor %}
+
+    2. If given a variable, check if that variable has changed. For example, the
+       following shows the date every time it changes, but only shows the hour if both
+       the hour and the date has changed::
+
+            {% for date in days %}
+                {% ifchanged date.date %} {{date.date}} {% endifchanged %}
+                {% ifchanged date.hour date.date %}
+                    {{date.hour}}
+                {% endifchanged %}
+            {% endfor %}
     """
     bits = token.contents.split()
-    if len(bits) != 1:
-        raise TemplateSyntaxError, "'ifchanged' tag takes no arguments"
     nodelist = parser.parse(('endifchanged',))
     parser.delete_first_token()
-    return IfChangedNode(nodelist)
+    return IfChangedNode(nodelist, *bits[1:])
 ifchanged = register.tag(ifchanged)
 
 #@register.tag
     string "123" is less than the string "2", for example. If you don't want
     string comparison here, you will need to write your own validator.
 
+``NumberIsInRange``
+    Takes two boundary number, ``lower`` and ``upper`` and checks that the
+    field is greater than ``lower`` (if given) and less than ``upper`` (if
+    given).  
+    
+    Both checks are inclusive; that is, ``NumberIsInRange(10, 20)`` will allow
+    values of both 10 and 20.  This validator only checks numeric fields
+    (i.e. floats and integer fields).
+
 ``IsAPowerOf``
     Takes an integer argument and when called as a validator, checks that the
     field being validated is a power of the integer.

docs/settings.txt

 The name of the database to use. For SQLite, it's the full path to the database
 file.
 
+DATABASE_OPTIONS
+----------------
+
+Default: ``{}`` (Empty dictionary)
+
+Extra parameters to use when connecting to the database. Consult backend
+module's document for available keywords.
+
 DATABASE_PASSWORD
 -----------------
 
 environment variable, and it'll be up to you to ensure your processes are
 running in the correct environment.
 
+URL_VALIDATOR_USER_AGENT
+------------------------
+
+Default: ``Django/<version> (http://www.djangoproject.com/)``
+
+The string to use as the ``User-Agent`` header when checking to see if URLs
+exist (see the ``verify_exists`` option on URLField_).
+
+.. URLField: ../model_api/#urlfield
+
 USE_ETAGS
 ---------
 

docs/syndication_feeds.txt

     from django.contrib.syndication.feeds import Feed
     from chicagocrime.models import NewsItem
 
-    class SiteNewsFeed(Feed):
+    class LatestEntries(Feed):
         title = "Chicagocrime.org site news"
         link = "/sitenews/"
         description = "Updates on changes and additions to chicagocrime.org."
 put into those elements.
 
     * To specify the contents of ``<title>`` and ``<description>``, create
-      `Django templates`_ called ``feeds/sitenews_title.html`` and
-      ``feeds/sitenews_description.html``, where ``sitenews`` is the ``slug``
+      `Django templates`_ called ``feeds/latest_title.html`` and
+      ``feeds/latest_description.html``, where ``latest`` is the ``slug``
       specified in the URLconf for the given feed. Note the ``.html`` extension
       is required. The RSS system renders that template for each item, passing
       it two template context variables:
 
           * ``{{ obj }}`` -- The current object (one of whichever objects you
-            returned in ``items()``).
+            returned in ``items()``). 
           * ``{{ site }}`` -- A ``django.models.core.sites.Site`` object
             representing the current site. This is useful for
             ``{{ site.domain }}`` or ``{{ site.name }}``.
       Both ``get_absolute_url()`` and ``item_link()`` should return the item's
       URL as a normal Python string.
 
+    * For the LatestEntries example above, we could have very simple feed templates:
+
+          * latest_title.html::
+
+             {{ obj.title }}
+
+          * latest_description.html::
+
+             {{ obj.description }}
+
 .. _chicagocrime.org: http://www.chicagocrime.org/
 .. _object-relational mapper: http://www.djangoproject.com/documentation/db_api/
 .. _Django templates: http://www.djangoproject.com/documentation/templates/

docs/templates.txt

 
 Check if a value has changed from the last iteration of a loop.
 
-The ``ifchanged`` block tag is used within a loop. It checks its own rendered
-contents against its previous state and only displays its content if the value
-has changed::
+The 'ifchanged' block tag is used within a loop. It has two possible uses.
 
-    <h1>Archive for {{ year }}</h1>
+1. Checks its own rendered contents against its previous state and only
+   displays the content if it has changed. For example, this displays a list of
+   days, only displaying the month if it changes::
 
-    {% for day in days %}
-    {% ifchanged %}<h3>{{ day|date:"F" }}</h3>{% endifchanged %}
-    <a href="{{ day|date:"M/d"|lower }}/">{{ day|date:"j" }}</a>
-    {% endfor %}
+        <h1>Archive for {{ year }}</h1>
+
+        {% for date in days %}
+            {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
+            <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
+        {% endfor %}
+        
+2. If given a variable, check if that variable has changed. For example, the
+   following shows the date every time it changes, but only shows the hour if both
+   the hour and the date has changed::
+    
+        {% for date in days %}
+            {% ifchanged date.date %} {{date.date}} {% endifchanged %}
+            {% ifchanged date.hour date.date %}
+                {{date.hour}}
+            {% endifchanged %}
+        {% endfor %}
 
 ifequal
 ~~~~~~~

tests/modeltests/pagination/models.py

 >>> paginator.last_on_page(1)
 9
 
+# Add a few more records to test out the orphans feature.
+>>> for x in range(10, 13):
+...     Article(headline="Article %s" % x, pub_date=datetime(2006, 10, 6)).save()
+
+# With orphans set to 3 and 10 items per page, we should get all 12 items on a single page:
+>>> paginator = ObjectPaginator(Article.objects.all(), 10, orphans=3)
+>>> paginator.pages
+1
+
+# With orphans only set to 1, we should get two pages:
+>>> paginator = ObjectPaginator(Article.objects.all(), 10, orphans=1)
+>>> paginator.pages
+2
 """}

tests/regressiontests/templates/tests.py

             'ifchanged05': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (1, 2, 3)}, '1123123123'),
             'ifchanged06': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (2, 2, 2)}, '1222'),
             'ifchanged07': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% for y in numy %}{% ifchanged %}{{ y }}{% endifchanged %}{% endfor %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (2, 2, 2), 'numy': (3, 3, 3)}, '1233323332333'),
+            
+            # Test one parameter given to ifchanged.
+            'ifchanged-param01': ('{% for n in num %}{% ifchanged n %}..{% endifchanged %}{{ n }}{% endfor %}', { 'num': (1,2,3) }, '..1..2..3'),
+            'ifchanged-param02': ('{% for n in num %}{% for x in numx %}{% ifchanged n %}..{% endifchanged %}{{ x }}{% endfor %}{% endfor %}', { 'num': (1,2,3), 'numx': (5,6,7) }, '..567..567..567'),
+            
+            # Test multiple parameters to ifchanged.
+            'ifchanged-param03': ('{% for n in num %}{{ n }}{% for x in numx %}{% ifchanged x n %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1,1,2), 'numx': (5,6,6) }, '156156256'),
+            
+            # Test a date+hour like construct, where the hour of the last day
+            # is the same but the date had changed, so print the hour anyway.
+            'ifchanged-param04': ('{% for d in days %}{% ifchanged %}{{ d.day }}{% endifchanged %}{% for h in d.hours %}{% ifchanged d h %}{{ h }}{% endifchanged %}{% endfor %}{% endfor %}', {'days':[{'day':1, 'hours':[1,2,3]},{'day':2, 'hours':[3]},] }, '112323'),
+            
+            # Logically the same as above, just written with explicit
+            # ifchanged for the day.
+            'ifchanged-param04': ('{% for d in days %}{% ifchanged d.day %}{{ d.day }}{% endifchanged %}{% for h in d.hours %}{% ifchanged d.day h %}{{ h }}{% endifchanged %}{% endfor %}{% endfor %}', {'days':[{'day':1, 'hours':[1,2,3]},{'day':2, 'hours':[3]},] }, '112323'),
 
             ### IFEQUAL TAG ###########################################################
             'ifequal01': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 2}, ""),