Django

Code

Changeset 4157

Show
Ignore:
Timestamp:
12/04/06 14:52:14 (2 years ago)
Author:
jpellerin
Message:

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

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/multiple-db-support/django/conf/global_settings.py

    r4155 r4157  
    102102DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3. 
    103103DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3. 
     104DATABASE_OPTIONS = {}          # Set to empy dictionary for default. 
    104105 
    105106# Optional named database connections in addition to the default. 
     
    232233TRANSACTIONS_MANAGED = False 
    233234 
     235# The User-Agent string to use when checking for URL validity through the 
     236# isExistingURL validator. 
     237URL_VALIDATOR_USER_AGENT = "Django/0.96pre (http://www.djangoproject.com)" 
     238 
    234239############## 
    235240# MIDDLEWARE # 
  • django/branches/multiple-db-support/django/contrib/admin/media/js/dateparse.js

    r2448 r4157  
    170170            var d = new Date(); 
    171171            d.setYear(parseInt(bits[1])); 
     172            d.setMonth(parseInt(bits[2], 10) - 1); 
    172173            d.setDate(parseInt(bits[3], 10)); 
    173             d.setMonth(parseInt(bits[2], 10) - 1); 
    174174            return d; 
    175175        } 
  • django/branches/multiple-db-support/django/contrib/admin/templatetags/admin_modify.py

    r4155 r4157  
    178178 
    179179def auto_populated_field_script(auto_pop_fields, change = False): 
     180    t = [] 
    180181    for field in auto_pop_fields: 
    181         t = [] 
    182182        if change: 
    183183            t.append('document.getElementById("id_%s")._changed = true;' % field.name) 
  • django/branches/multiple-db-support/django/contrib/comments/models.py

    r3427 r4157  
    3535        Given a rating_string, this returns a tuple of (rating_range, options). 
    3636        >>> s = "scale:1-10|First_category|Second_category" 
    37         >>> get_rating_options(s) 
     37        >>> Comment.objects.get_rating_options(s) 
    3838        ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ['First category', 'Second category']) 
    3939        """ 
  • django/branches/multiple-db-support/django/core/management.py

    r4156 r4157  
    186186get_sql_reset.help_doc = "Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given app name(s)." 
    187187get_sql_reset.args = APP_ARGS 
     188 
     189def get_sql_initial_data_for_model(model): 
     190    from django.db import models 
     191    from django.conf import settings 
     192 
     193    opts = model._meta 
     194    app_dir = os.path.normpath(os.path.join(os.path.dirname(models.get_app(model._meta.app_label).__file__), 'sql')) 
     195    output = [] 
     196 
     197    # Some backends can't execute more than one SQL statement at a time, 
     198    # so split into separate statements. 
     199    statements = re.compile(r";[ \t]*$", re.M) 
     200 
     201    # Find custom SQL, if it's available. 
     202    sql_files = [os.path.join(app_dir, "%s.%s.sql" % (opts.object_name.lower(), settings.DATABASE_ENGINE)), 
     203                 os.path.join(app_dir, "%s.sql" % opts.object_name.lower())] 
     204    for sql_file in sql_files: 
     205        if os.path.exists(sql_file): 
     206            fp = open(sql_file, 'U') 
     207            for statement in statements.split(fp.read()): 
     208                # Remove any comments from the file 
     209                statement = re.sub(r"--.*[\n\Z]", "", statement) 
     210                if statement.strip(): 
     211                    output.append(statement + ";") 
     212            fp.close() 
     213 
     214    return output 
    188215 
    189216def get_sql_initial_data(app): 
  • django/branches/multiple-db-support/django/core/paginator.py

    r3427 r4157  
    1 from math import ceil 
    2  
    31class InvalidPage(Exception): 
    42    pass 
     
    64class ObjectPaginator(object): 
    75    """ 
    8     This class makes pagination easy. Feed it a QuerySet, plus the number of 
    9     objects you want on each page. Then read the hits and pages properties to 
     6    This class makes pagination easy. Feed it a QuerySet or list, plus the number 
     7    of objects you want on each page. Then read the hits and pages properties to 
    108    see how many pages it involves. Call get_page with a page number (starting 
    119    at 0) to get back a list of objects for that page. 
     
    1311    Finally, check if a page number has a next/prev page using 
    1412    has_next_page(page_number) and has_previous_page(page_number). 
     13     
     14    Use orphans to avoid small final pages. For example: 
     15    13 records, num_per_page=10, orphans=2 --> pages==2, len(self.get_page(0))==10 
     16    12 records, num_per_page=10, orphans=2 --> pages==1, len(self.get_page(0))==12 
    1517    """ 
    16     def __init__(self, query_set, num_per_page): 
     18    def __init__(self, query_set, num_per_page, orphans=0): 
    1719        self.query_set = query_set 
    1820        self.num_per_page = num_per_page 
    19         self._hits, self._pages = None, None 
    20         self._has_next = {} # Caches page_number -> has_next_boolean 
     21        self.orphans = orphans 
     22        self._hits = self._pages = None 
    2123 
    22     def get_page(self, page_number): 
     24    def validate_page_number(self, page_number): 
    2325        try: 
    2426            page_number = int(page_number) 
    2527        except ValueError: 
    2628            raise InvalidPage 
    27         if page_number < 0
     29        if page_number < 0 or page_number > self.pages - 1
    2830            raise InvalidPage 
     31        return page_number 
    2932 
    30         # Retrieve one extra record, and check for the existence of that extra 
    31         # record to determine whether there's a next page. 
    32         limit = self.num_per_page + 1 
    33         offset = page_number * self.num_per_page 
    34  
    35         object_list = list(self.query_set[offset:offset+limit]) 
    36  
    37         if not object_list: 
    38             raise InvalidPage 
    39  
    40         self._has_next[page_number] = (len(object_list) > self.num_per_page) 
    41         return object_list[:self.num_per_page] 
     33    def get_page(self, page_number): 
     34        page_number = self.validate_page_number(page_number) 
     35        bottom = page_number * self.num_per_page 
     36        top = bottom + self.num_per_page 
     37        if top + self.orphans >= self.hits: 
     38            top = self.hits 
     39        return self.query_set[bottom:top] 
    4240 
    4341    def has_next_page(self, page_number): 
    4442        "Does page $page_number have a 'next' page?" 
    45         if not self._has_next.has_key(page_number): 
    46             if self._pages is None: 
    47                 offset = (page_number + 1) * self.num_per_page 
    48                 self._has_next[page_number] = len(self.query_set[offset:offset+1]) > 0 
    49             else: 
    50                 self._has_next[page_number] = page_number < (self.pages - 1) 
    51         return self._has_next[page_number] 
     43        return page_number < self.pages - 1 
    5244 
    5345    def has_previous_page(self, page_number): 
     
    5951        relative to total objects found (hits). 
    6052        """ 
    61         if page_number == 0: 
    62             return 1 
     53        page_number = self.validate_page_number(page_number) 
    6354        return (self.num_per_page * page_number) + 1 
    6455 
     
    6859        relative to total objects found (hits). 
    6960        """ 
    70         if page_number == 0 and self.num_per_page >= self._hits: 
    71             return self._hits 
    72         elif page_number == (self._pages - 1) and (page_number + 1) * self.num_per_page > self._hits: 
    73             return self._hits 
    74         return (page_number + 1) * self.num_per_page 
     61        page_number = self.validate_page_number(page_number) 
     62        page_number += 1   # 1-base 
     63        if page_number == self.pages: 
     64            return self.hits 
     65        return page_number * self.num_per_page 
    7566 
    7667    def _get_hits(self): 
    7768        if self._hits is None: 
    78             self._hits = self.query_set.count() 
     69            # Try .count() or fall back to len(). 
     70            try: 
     71                self._hits = int(self.query_set.count()) 
     72            except (AttributeError, TypeError, ValueError): 
     73                # AttributeError if query_set has no object count. 
     74                # TypeError if query_set.count() required arguments. 
     75                # ValueError if int() fails. 
     76                self._hits = len(self.query_set) 
    7977        return self._hits 
    8078 
    8179    def _get_pages(self): 
    8280        if self._pages is None: 
    83             self._pages = int(ceil(self.hits / float(self.num_per_page))) 
     81            hits = (self.hits - 1 - self.orphans) 
     82            if hits < 1: 
     83                hits = 0 
     84            self._pages = hits // self.num_per_page + 1 
    8485        return self._pages 
    8586 
  • django/branches/multiple-db-support/django/core/serializers/base.py

    r3712 r4157  
    2929 
    3030        self.stream = options.get("stream", StringIO()) 
     31        self.selected_fields = options.get("fields") 
    3132 
    3233        self.start_serialization() 
     
    3738                    continue 
    3839                elif field.rel is None: 
    39                     self.handle_field(obj, field) 
     40                    if self.selected_fields is None or field.attname in self.selected_fields: 
     41                        self.handle_field(obj, field) 
    4042                else: 
    41                     self.handle_fk_field(obj, field) 
     43                    if self.selected_fields is None or field.attname[:-3] in self.selected_fields: 
     44                        self.handle_fk_field(obj, field) 
    4245            for field in obj._meta.many_to_many: 
    43                 self.handle_m2m_field(obj, field) 
     46                if self.selected_fields is None or field.attname in self.selected_fields: 
     47                    self.handle_m2m_field(obj, field) 
    4448            self.end_object(obj) 
    4549        self.end_serialization() 
  • django/branches/multiple-db-support/django/core/serializers/python.py

    r3427 r4157  
    7777                 
    7878            # Handle FK fields 
    79             elif field.rel and isinstance(field.rel, models.ManyToOneRel)
     79            elif field.rel and isinstance(field.rel, models.ManyToOneRel) and field_value is not None
    8080                try: 
    8181                    data[field.name] = field.rel.to._default_manager.get(pk=field_value) 
  • django/branches/multiple-db-support/django/core/serializers/xml_serializer.py

    r3258 r4157  
    167167        # validation error, but that's expected). 
    168168        RelatedModel = self._get_model_from_node(node, "to") 
    169         return RelatedModel.objects.get(pk=getInnerText(node).strip().encode(self.encoding)) 
     169        # Check if there is a child node named 'None', returning None if so. 
     170        if len(node.childNodes) == 1 and node.childNodes[0].nodeName == 'None': 
     171            return None 
     172        else: 
     173            return RelatedModel.objects.get(pk=getInnerText(node).strip().encode(self.encoding)) 
    170174         
    171175    def _handle_m2m_field_node(self, node): 
  • django/branches/multiple-db-support/django/core/servers/fastcgi.py

    r4156 r4157  
    3434  maxrequests=NUMBER   number of requests a child handles before it is  
    3535                       killed and a new child is forked (0 = no limit). 
    36   maxspare=NUMBER      max number of spare processes to keep running. 
    37   minspare=NUMBER      min number of spare processes to prefork
    38   maxchildren=NUMBER   hard limit number of processes in prefork mode. 
     36  maxspare=NUMBER      max number of spare processes / threads 
     37  minspare=NUMBER      min number of spare processes / threads
     38  maxchildren=NUMBER   hard limit number of processes / threads 
    3939  daemonize=BOOL       whether to detach from terminal. 
    4040  pidfile=FILE         write the spawned process-id to this file. 
     
    111111    elif options['method'] in ('thread', 'threaded'): 
    112112        from flup.server.fcgi import WSGIServer 
    113         wsgi_opts = {} 
     113        wsgi_opts = { 
     114            'maxSpare': int(options["maxspare"]), 
     115            'minSpare': int(options["minspare"]), 
     116            'maxThreads': int(options["maxchildren"]), 
     117        } 
    114118    else: 
    115119        return fastcgi_help("ERROR: Implementation must be one of prefork or thread.") 
  • django/branches/multiple-db-support/django/core/urlresolvers.py

    r4156 r4157  
    2222    # Converts 'django.views.news.stories.story_detail' to 
    2323    # ['django.views.news.stories', 'story_detail'] 
    24     dot = callback.rindex('.') 
     24    try: 
     25        dot = callback.rindex('.') 
     26    except ValueError: 
     27        return callback, '' 
    2528    return callback[:dot], callback[dot+1:] 
    2629 
  • django/branches/multiple-db-support/django/core/validators.py

    r4142 r4157  
    99""" 
    1010 
     11import urllib2 
    1112from django.conf import settings 
    1213from django.utils.translation import gettext, gettext_lazy, ngettext 
     
    224225 
    225226def isExistingURL(field_data, all_data): 
    226     import urllib2 
    227     try: 
    228         u = urllib2.urlopen(field_data) 
     227    try: 
     228        headers = { 
     229            "Accept" : "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", 
     230            "Accept-Language" : "en-us,en;q=0.5", 
     231            "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7", 
     232            "Connection" : "close", 
     233            "User-Agent": settings.URL_VALIDATOR_USER_AGENT 
     234            } 
     235        req = urllib2.Request(field_data,None, headers) 
     236        u = urllib2.urlopen(req) 
    229237    except ValueError: 
    230         raise ValidationError, gettext("Invalid URL: %s") % field_data 
     238        raise ValidationError, _("Invalid URL: %s") % field_data 
    231239    except urllib2.HTTPError, e: 
    232240        # 401s are valid; they just mean authorization is required. 
    233         if e.code not in ('401',): 
    234             raise ValidationError, gettext("The URL %s is a broken link.") % field_data 
     241        # 301 and 302 are redirects; they just mean look somewhere else. 
     242        if str(e.code) not in ('401','301','302'): 
     243            raise ValidationError, _("The URL %s is a broken link.") % field_data 
    235244    except: # urllib2.URLError, httplib.InvalidURL, etc. 
    236         raise ValidationError, gettext("The URL %s is a broken link.") % field_data 
    237  
     245        raise ValidationError, _("The URL %s is a broken link.") % field_data 
     246         
    238247def isValidUSState(field_data, all_data): 
    239248    "Checks that the given string is a valid two-letter U.S. state abbreviation" 
     
    344353            if field_name != self.field_name and value == field_data: 
    345354                raise ValidationError, self.error_message 
     355 
     356class NumberIsInRange(object): 
     357    """ 
     358    Validator that tests if a value is in a range (inclusive). 
     359    """ 
     360    def __init__(self, lower=None, upper=None, error_message=''): 
     361        self.lower, self.upper = lower, upper 
     362        if not error_message: 
     363            if lower and upper: 
     364                self.error_message = gettext("This value must be between %s and %s.") % (lower, upper) 
     365            elif lower: 
     366                self.error_message = gettext("This value must be at least %s.") % lower 
     367            elif upper: 
     368                self.error_message = gettext("This value must be no more than %s.") % upper 
     369        else: 
     370            self.error_message = error_message 
     371 
     372    def __call__(self, field_data, all_data): 
     373        # Try to make the value numeric. If this fails, we assume another  
     374        # validator will catch the problem. 
     375        try: 
     376            val = float(field_data) 
     377        except ValueError: 
     378            return 
     379             
     380        # Now validate 
     381        if self.lower and self.upper and (val < self.lower or val > self.upper): 
     382            raise ValidationError(self.error_message) 
     383        elif self.lower and val < self.lower: 
     384            raise ValidationError(self.error_message) 
     385        elif self.upper and val > self.upper: 
     386            raise ValidationError(self.error_message) 
    346387 
    347388class IsAPowerOf(object): 
  • django/branches/multiple-db-support/django/db/backends/ado_mssql/base.py

    r4127 r4157  
    5151    def __init__(self, settings): 
    5252        self.settings = settings 
     53        self.options = settings.DATABASE_OPTIONS 
    5354        self.connection = None 
    5455        self.queries = [] 
  • django/branches/multiple-db-support/django/db/backends/ansi/sql.py

    r4142 r4157  
    292292                else:                                  
    293293                    for statement in statements.split(fp.read()): 
     294                        # Remove any comments from the file 
     295                        statement = re.sub(r"--.*[\n\Z]", "", statement) 
    294296                        if statement.strip(): 
    295297                            output.append(BoundStatement(statement + ";", 
  • django/branches/multiple-db-support/django/db/backends/mysql/base.py

    r4151 r4157  
    6565        self.queries = [] 
    6666        self.server_version = None 
     67        self.options = settings.DATABASE_OPTIONS 
    6768 
    6869    def _valid_connection(self): 
     
    9192            if settings.DATABASE_PORT: 
    9293                kwargs['port'] = int(settings.DATABASE_PORT) 
     94            kwargs.update(self.options) 
    9395            self.connection = Database.connect(**kwargs) 
    9496        cursor = self.connection.cursor() 
  • django/branches/multiple-db-support/django/db/backends/oracle/base.py

    r4127 r4157  
    1919        self.connection = None 
    2020        self.queries = [] 
     21        self.options = settings.DATABASE_OPTIONS 
    2122 
    2223    def _valid_connection(self): 
     
    3031            if len(settings.DATABASE_PORT.strip()) != 0: 
    3132                dsn = Database.makedsn(settings.DATABASE_HOST, int(settings.DATABASE_PORT), settings.DATABASE_NAME) 
    32                 self.connection = Database.connect(settings.DATABASE_USER, settings.DATABASE_PASSWORD, dsn
     33                self.connection = Database.connect(settings.DATABASE_USER, settings.DATABASE_PASSWORD, dsn, **self.options
    3334            else: 
    3435                conn_string = "%s/%s@%s" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME) 
    35                 self.connection = Database.connect(conn_string
     36                self.connection = Database.connect(conn_string, **self.options
    3637        return FormatStylePlaceholderCursor(self.connection) 
    3738 
  • django/branches/multiple-db-support/django/db/backends/postgresql/base.py

    r4127 r4157  
    1919        self.connection = None 
    2020        self.queries = [] 
     21        self.options = settings.DATABASE_OPTIONS 
    2122 
    2223    def cursor(self): 
     
    3536            if settings.DATABASE_PORT: 
    3637                conn_string += " port=%s" % settings.DATABASE_PORT 
    37             self.connection = Database.connect(conn_string
     38            self.connection = Database.connect(conn_string, **self.options
    3839            self.connection.set_isolation_level(1) # make transactions transparent to all cursors 
    3940        cursor = self.connection.cursor() 
  • django/branches/multiple-db-support/django/db/backends/postgresql_psycopg2/base.py

    r4127 r4157  
    1919        self.connection = None 
    2020        self.queries = [] 
     21        self.options = settings.DATABASE_OPTIONS 
    2122 
    2223    def cursor(self): 
     
    3536            if settings.DATABASE_PORT: 
    3637                conn_string += " port=%s" % settings.DATABASE_PORT 
    37             self.connection = Database.connect(conn_string
     38            self.connection = Database.connect(conn_string, **self.options
    3839            self.connection.set_isolation_level(1) # make transactions transparent to all cursors 
    3940        cursor = self.connection.cursor() 
  • django/branches/multiple-db-support/django/db/backends/sqlite3/base.py

    r4141 r4157  
    4747        self.connection = None 
    4848        self.queries = [] 
     49        self.options = settings.DATABASE_OPTIONS 
    4950 
    5051    def cursor(self): 
    5152        settings = self.settings 
    5253        if self.connection is None: 
    53             self.connection = Database.connect(settings.DATABASE_NAME, 
    54                 detect_types=Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES) 
    55  
     54            kwargs = { 
     55                'database': settings.DATABASE_NAME, 
     56                'detect_types': Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES, 
     57            } 
     58            kwargs.update(self.options) 
     59            self.connection = Database.connect(**kwargs) 
    5660            # Register extract and date_trunc functions. 
    5761            self.connection.create_function("django_extract", 2, _sqlite_extract) 
  • django/branches/multiple-db-support/django/db/backends/util.py

    r4139 r4157  
    1818                params = tuple(params) 
    1919            self.db.queries.append({ 
    20                 'sql': sql % tuple(params)
     20                'sql': sql % params
    2121                'time': "%.3f" % (stop - start), 
    2222            }) 
  • django/branches/multiple-db-support/django/db/__init__.py

    r4156 r4157  
    2929if not settings.DATABASE_ENGINE: 
    3030    settings.DATABASE_ENGINE = 'dummy' 
    31  
     31     
    3232 
    3333def connect(settings, **kw): 
     
    4949        if settings is None: 
    5050            from django.conf import settings 
     51        if not settings.DATABASE_OPTIONS: 
     52            settings.DATABASE_OPTIONS = {} 
    5153        self.settings = settings 
    5254        self.backend = self.load_backend() 
  • django/branches/multiple-db-support/django/db/models/fields/__init__.py

    r4156 r4157  
    592592        def isWithinMediaRoot(field_data, all_data): 
    593593            f = os.path.abspath(os.path.join(settings.MEDIA_ROOT, field_data)) 
    594             if not f.startswith(os.path.normpath(settings.MEDIA_ROOT)): 
     594            if not f.startswith(os.path.abspath(os.path.normpath(settings.MEDIA_ROOT))): 
    595595                raise validators.ValidationError, _("Enter a valid filename.") 
    596596        field_list[1].validator_list.append(isWithinMediaRoot) 
  • django/branches/multiple-db-support/django/db/models/manipulators.py

    r4155 r4157  
    287287        # form fields, e.g. DateTime. 
    288288        # This validation needs to occur after html2python to be effective. 
    289         field_val = all_data.get(f.attname, None) 
     289        field_val = all_data.get(f.name, None) 
    290290        if field_val is None: 
    291291            # This will be caught by another validator, assuming the field 
  • django/branches/multiple-db-support/django/forms/__init__.py

    r4142 r4157  
    109109    prepopulated data and validation error messages to the formfield objects. 
    110110    """ 
    111     def __init__(self, manipulator, data, error_dict, edit_inline=True): 
    112         self.manipulator, self.data = manipulator, data 
     111    def __init__(self, manipulator, data=None, error_dict=None, edit_inline=True): 
     112        self.manipulator = manipulator 
     113        if data is None: 
     114            data = {} 
     115        if error_dict is None: 
     116            error_dict = {} 
     117        self.data = data 
    113118        self.error_dict = error_dict 
    114119        self._inline_collections = None 
  • django/branches/multiple-db-support/django/newforms/fields.py

    r4156 r4157  
    189189    r'(?:/?|/\S+)$', re.IGNORECASE) 
    190190 
     191try: 
     192    from django.conf import settings 
     193    URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT 
     194except ImportError: 
     195    # It's OK if Django settings aren't configured. 
     196    URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)' 
     197 
    191198class URLField(RegexField): 
    192     def __init__(self, required=True, verify_exists=False, widget=None): 
     199    def __init__(self, required=True, verify_exists=False, widget=None, 
     200            validator_user_agent=URL_VALIDATOR_USER_AGENT): 
    193201        RegexField.__init__(self, url_re, u'Enter a valid URL.', required, widget) 
    194202        self.verify_exists = verify_exists 
     203        self.user_agent = validator_user_agent 
    195204 
    196205    def clean(self, value): 
     
    198207        if self.verify_exists: 
    199208            import urllib2 
     209            from django.conf import settings 
     210            headers = { 
     211                "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", 
     212                "Accept-Language": "en-us,en;q=0.5", 
     213                "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7", 
     214                "Connection": "close", 
     215                "User-Agent": self.user_agent, 
     216            } 
    200217            try: 
    201                 u = urllib2.urlopen(value) 
     218                req = urllib2.Request(field_data, None, headers) 
     219                u = urllib2.urlopen(req) 
    202220            except ValueError: 
    203221                raise ValidationError(u'Enter a valid URL.') 
  • django/branches/multiple-db-support/django/template/defaultfilters.py

    r4139 r4157  
    422422    bytes, etc). 
    423423    """ 
    424     bytes = float(bytes) 
     424    try: 
     425        bytes = float(bytes) 
     426    except TypeError: 
     427        return "0 bytes" 
     428         
    425429    if bytes < 1024: 
    426430        return "%d byte%s" % (bytes, bytes != 1 and 's' or '') 
  • django/branches/multiple-db-support/django/template/defaulttags.py

    r4155 r4157  
    125125 
    126126class IfChangedNode(Node): 
    127     def __init__(self, nodelist): 
     127    def __init__(self, nodelist, *varlist): 
    128128        self.nodelist = nodelist 
    129129        self._last_seen = None 
     130        self._varlist = varlist 
    130131 
    131132    def render(self, context): 
    132133        if context.has_key('forloop') and context['forloop']['first']: 
    133134            self._last_seen = None 
    134         content = self.nodelist.render(context) 
    135         if content != self._last_seen: 
     135        try: 
     136            if self._varlist: 
     137                # Consider multiple parameters. 
     138                # This automatically behaves like a OR evaluation of the multiple variables. 
     139                compare_to = [resolve_variable(var, context) for var in self._varlist] 
     140            else: 
     141                compare_to = self.nodelist.render(context) 
     142        except VariableDoesNotExist: 
     143            compare_to = None         
     144 
     145        if  compare_to != self._last_seen: 
    136146            firstloop = (self._last_seen == None) 
    137             self._last_seen = content 
     147            self._last_seen = compare_to 
    138148            context.push() 
    139149            context['ifchanged'] = {'firstloop': firstloop} 
     
    635645    Check if a value has changed from the last iteration of a loop. 
    636646 
    637     The 'ifchanged' block tag is used within a loop. It checks its own rendered 
    638     contents against its previous state and only displays its content if the 
    639     value has changed:: 
    640  
    641         <h1>Archive for {{ year }}</h1> 
    642  
    643         {% for date in days %} 
    644         {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %} 
    645         <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a> 
    646         {% endfor %} 
     647    The 'ifchanged' block tag is used within a loop. It has two possible uses. 
     648 
     649    1. Checks its own rendered contents against its previous state and only 
     650       displays the content if it has changed. For example, this displays a list of 
     651       days, only displaying the month if it changes:: 
     652 
     653            <h1>Archive for {{ year }}</h1> 
     654 
     655            {% for date in days %} 
     656                {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %} 
     657                <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a> 
     658            {% endfor %} 
     659 
     660    2. If given a variable, check if that variable has changed. For example, the 
     661       following shows the date every time it changes, but only shows the hour if both 
     662       the hour and the date has changed:: 
     663 
     664            {% for date in days %} 
     665                {% ifchanged date.date %} {{date.date}} {% endifchanged %} 
     666                {% ifchanged date.hour date.date %} 
     667                    {{date.hour}} 
     668                {% endifchanged %} 
     669            {% endfor %} 
    647670    """ 
    648671    bits = token.contents.split() 
    649     if len(bits) != 1: 
    650         raise TemplateSyntaxError, "'ifchanged' tag takes no arguments" 
    651672    nodelist = parser.parse(('endifchanged',)) 
    652673    parser.delete_first_token() 
    653     return IfChangedNode(nodelist
     674    return IfChangedNode(nodelist, *bits[1:]
    654675ifchanged = register.tag(ifchanged) 
    655676 
  • django/branches/multiple-db-support/django/template/__init__.py

    r4156 r4157  
    869869 
    870870                    if not getattr(self, 'nodelist', False): 
    871                         from django.template.loader import get_template 
    872                         t = get_template(file_name) 
     871                        from django.template.loader import get_template, select_template 
     872                        if hasattr(file_name, '__iter__'): 
     873                            t = select_template(file_name) 
     874                        else: 
     875                            t = get_template(file_name) 
    873876                        self.nodelist = t.nodelist 
    874877                    return self.nodelist.render(context_class(dict))