Django

Code

Changeset 4063

Show
Ignore:
Timestamp:
11/10/06 09:42:40 (2 years ago)
Author:
jkocherhans
Message:

[generic-auth] Merged to [4062]

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/generic-auth/django/conf/global_settings.py

    r4026 r4063  
    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 empty dictionary for default. 
    104105 
    105106# Host for sending e-mail. 
     
    229230TRANSACTIONS_MANAGED = False 
    230231 
     232# The User-Agent string to use when checking for URL validity through the 
     233# isExistingURL validator. 
     234URL_VALIDATOR_USER_AGENT = "Django/0.96pre (http://www.djangoproject.com)" 
     235 
    231236############## 
    232237# MIDDLEWARE # 
  • django/branches/generic-auth/django/contrib/admin/media/js/dateparse.js

    r2448 r4063  
    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/generic-auth/django/contrib/admin/templatetags/admin_modify.py

    r4026 r4063  
    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/generic-auth/django/contrib/comments/models.py

    r4024 r4063  
    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/generic-auth/django/core/mail.py

    r2984 r4063  
    55from email.Header import Header 
    66import smtplib, rfc822 
     7import socket 
     8import time 
     9import random 
     10 
     11DNS_NAME = socket.getfqdn() # Cache the hostname 
    712 
    813class BadHeaderError(ValueError): 
     
    5257        msg['Date'] = rfc822.formatdate() 
    5358        try: 
     59            random_bits = str(random.getrandbits(64)) 
     60        except AttributeError: # Python 2.3 doesn't have random.getrandbits(). 
     61            random_bits = ''.join([random.choice('1234567890') for i in range(19)]) 
     62        msg['Message-ID'] = "<%d.%d@%s>" % (time.time(), random_bits, DNS_NAME) 
     63        try: 
    5464            server.sendmail(from_email, recipient_list, msg.as_string()) 
    5565            num_sent += 1 
  • django/branches/generic-auth/django/core/management.py

    r4026 r4063  
    350350            fp = open(sql_file, 'U') 
    351351            for statement in statements.split(fp.read()): 
     352                # Remove any comments from the file 
     353                statement = re.sub(r"--.*[\n\Z]", "", statement) 
    352354                if statement.strip(): 
    353355                    output.append(statement + ";") 
  • django/branches/generic-auth/django/core/paginator.py

    r4024 r4063  
    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/generic-auth/django/core/serializers/base.py

    r4026 r4063  
    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/generic-auth/django/core/serializers/python.py

    r4024 r4063  
    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/generic-auth/django/core/serializers/xml_serializer.py

    r4024 r4063  
    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/generic-auth/django/core/servers/fastcgi.py

    r4026 r4063  
    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/generic-auth/django/core/urlresolvers.py

    r4026 r4063  
    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/generic-auth/django/core/validators.py

    r4026 r4063  
    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/generic-auth/django/db/backends/ado_mssql/base.py

    r3115 r4063  
    5656 
    5757class DatabaseWrapper(local): 
    58     def __init__(self): 
     58    def __init__(self, **kwargs): 
    5959        self.connection = None 
    6060        self.queries = [] 
  • django/branches/generic-auth/django/db/backends/dummy/base.py

    r3073 r4063  
    2121    _rollback = complain 
    2222 
     23    def __init__(self, **kwargs): 
     24        pass 
     25 
    2326    def close(self): 
    2427        pass # close() 
  • django/branches/generic-auth/django/db/backends/mysql/base.py

    r4026 r4063  
    6666 
    6767class DatabaseWrapper(local): 
    68     def __init__(self): 
     68    def __init__(self, **kwargs): 
    6969        self.connection = None 
    7070        self.queries = [] 
    7171        self.server_version = None 
     72        self.options = kwargs 
    7273 
    7374    def _valid_connection(self): 
     
    9697            if settings.DATABASE_PORT: 
    9798                kwargs['port'] = int(settings.DATABASE_PORT) 
     99            kwargs.update(self.options) 
    98100            self.connection = Database.connect(**kwargs) 
    99101        cursor = self.connection.cursor() 
  • django/branches/generic-auth/django/db/backends/oracle/base.py

    r4024 r4063  
    2222 
    2323class DatabaseWrapper(local): 
    24     def __init__(self): 
     24    def __init__(self, **kwargs): 
    2525        self.connection = None 
    2626        self.queries = [] 
     27        self.options = kwargs 
    2728 
    2829    def _valid_connection(self): 
     
    3637            if len(settings.DATABASE_PORT.strip()) != 0: 
    3738                dsn = Database.makedsn(settings.DATABASE_HOST, int(settings.DATABASE_PORT), settings.DATABASE_NAME) 
    38                 self.connection = Database.connect(settings.DATABASE_USER, settings.DATABASE_PASSWORD, dsn
     39                self.connection = Database.connect(settings.DATABASE_USER, settings.DATABASE_PASSWORD, dsn, **self.options
    3940            else: 
    4041                conn_string = "%s/%s@%s" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME) 
    41                 self.connection = Database.connect(conn_string
     42                self.connection = Database.connect(conn_string, **self.options
    4243        return FormatStylePlaceholderCursor(self.connection) 
    4344 
  • django/branches/generic-auth/django/db/backends/postgresql/base.py

    r3115 r4063  
    2222 
    2323class DatabaseWrapper(local): 
    24     def __init__(self): 
     24    def __init__(self, **kwargs): 
    2525        self.connection = None 
    2626        self.queries = [] 
     27        self.options = kwargs 
    2728 
    2829    def cursor(self): 
     
    4142            if settings.DATABASE_PORT: 
    4243                conn_string += " port=%s" % settings.DATABASE_PORT 
    43             self.connection = Database.connect(conn_string
     44            self.connection = Database.connect(conn_string, **self.options
    4445            self.connection.set_isolation_level(1) # make transactions transparent to all cursors 
    4546        cursor = self.connection.cursor() 
  • django/branches/generic-auth/django/db/backends/postgresql_psycopg2/base.py

    r4026 r4063  
    2222 
    2323class DatabaseWrapper(local): 
    24     def __init__(self): 
     24    def __init__(self, **kwargs): 
    2525        self.connection = None 
    2626        self.queries = [] 
     27        self.options = kwargs 
    2728 
    2829    def cursor(self): 
     
    4142            if settings.DATABASE_PORT: 
    4243                conn_string += " port=%s" % settings.DATABASE_PORT 
    43             self.connection = Database.connect(conn_string
     44            self.connection = Database.connect(conn_string, **self.options
    4445            self.connection.set_isolation_level(1) # make transactions transparent to all cursors 
    4546        cursor = self.connection.cursor() 
  • django/branches/generic-auth/django/db/backends/sqlite3/base.py

    r4026 r4063  
    4343 
    4444class DatabaseWrapper(local): 
    45     def __init__(self): 
     45    def __init__(self, **kwargs): 
    4646        self.connection = None 
    4747        self.queries = [] 
     48        self.options = kwargs 
    4849 
    4950    def cursor(self): 
    5051        from django.conf import settings 
    5152        if self.connection is None: 
    52             self.connection = Database.connect(settings.DATABASE_NAME, 
    53                 detect_types=Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES) 
    54  
     53            kwargs = { 
     54                'database': settings.DATABASE_NAME, 
     55                'detect_types': Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES, 
     56            } 
     57            kwargs.update(self.options) 
     58            self.connection = Database.connect(**kwargs) 
    5559            # Register extract and date_trunc functions. 
    5660            self.connection.create_function("django_extract", 2, _sqlite_extract) 
  • django/branches/generic-auth/django/db/backends/util.py

    r4026 r4063  
    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/generic-auth/django/db/__init__.py

    r4026 r4063  
    2828runshell = lambda: __import__('django.db.backends.%s.client' % settings.DATABASE_ENGINE, {}, {}, ['']).runshell() 
    2929 
    30 connection = backend.DatabaseWrapper(
     30connection = backend.DatabaseWrapper(**settings.DATABASE_OPTIONS
    3131DatabaseError = backend.DatabaseError 
    3232 
  • django/branches/generic-auth/django/db/models/fields/__init__.py

    r4026 r4063  
    586586        def isWithinMediaRoot(field_data, all_data): 
    587587            f = os.path.abspath(os.path.join(settings.MEDIA_ROOT, field_data)) 
    588             if not f.startswith(os.path.normpath(settings.MEDIA_ROOT)): 
     588            if not f.startswith(os.path.abspath(os.path.normpath(settings.MEDIA_ROOT))): 
    589589                raise validators.ValidationError, _("Enter a valid filename.") 
    590590        field_list[1].validator_list.append(isWithinMediaRoot) 
  • django/branches/generic-auth/django/db/models/manipulators.py

    r4026 r4063  
    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/generic-auth/django/forms/__init__.py

    r4026 r4063  
    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/generic-auth/django/newforms/fields.py

    r4026 r4063  
    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(value, None, headers) 
     219                u = urllib2.urlopen(req) 
    202220            except ValueError: 
    203221                raise ValidationError(u'Enter a valid URL.') 
  • django/branches/generic-auth/django/template/defaultfilters.py

    r4026 r4063  
    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/generic-auth/django/template/defaulttags.py

    r4026 r4063  
    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 whether 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 have 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/generic-auth/django/template/__init__.py

    r4026 r4063  
    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