Django

Code

Changeset 7935

Show
Ignore:
Timestamp:
07/16/08 14:21:15 (4 months ago)
Author:
brosner
Message:

newforms-admin: Fixed #5490 -- Properly quote special characters in primary keys in the admin. Added tests to ensure functionality. This also moves quote and unquote to django/contrib/admin/util.py. Thanks jdetaeye and shanx for all your help.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/newforms-admin/django/contrib/admin/models.py

    r6776 r7935  
    22from django.contrib.contenttypes.models import ContentType 
    33from django.contrib.auth.models import User 
     4from django.contrib.admin.util import quote 
    45from django.utils.translation import ugettext_lazy as _ 
    56from django.utils.encoding import smart_unicode 
     
    5152        This is relative to the Django admin index page. 
    5253        """ 
    53         return mark_safe(u"%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, self.object_id)) 
     54        return mark_safe(u"%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, quote(self.object_id))) 
  • django/branches/newforms-admin/django/contrib/admin/options.py

    r7932 r7935  
    66from django.contrib.contenttypes.models import ContentType 
    77from django.contrib.admin import widgets 
    8 from django.contrib.admin.util import get_deleted_objects 
     8from django.contrib.admin.util import quote, unquote, get_deleted_objects 
    99from django.core.exceptions import ImproperlyConfigured, PermissionDenied 
    1010from django.db import models, transaction 
     
    2424class IncorrectLookupParameters(Exception): 
    2525    pass 
    26  
    27 def unquote(s): 
    28     """ 
    29     Undo the effects of quote(). Based heavily on urllib.unquote(). 
    30     """ 
    31     mychr = chr 
    32     myatoi = int 
    33     list = s.split('_') 
    34     res = [list[0]] 
    35     myappend = res.append 
    36     del list[0] 
    37     for item in list: 
    38         if item[1:2]: 
    39             try: 
    40                 myappend(mychr(myatoi(item[:2], 16)) + item[2:]) 
    41             except ValueError: 
    42                 myappend('_' + item) 
    43         else: 
    44             myappend('_' + item) 
    45     return "".join(res) 
    4626 
    4727def flatten_fieldsets(fieldsets): 
     
    657637        # Populate deleted_objects, a data structure of all related objects that 
    658638        # will also be deleted. 
    659         deleted_objects = [mark_safe(u'%s: <a href="../../%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), force_unicode(object_id), escape(obj))), []] 
     639        deleted_objects = [mark_safe(u'%s: <a href="../../%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), quote(object_id), escape(obj))), []] 
    660640        perms_needed = sets.Set() 
    661641        get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1, self.admin_site) 
  • django/branches/newforms-admin/django/contrib/admin/util.py

    r7685 r7935  
    66from django.utils.encoding import force_unicode 
    77from django.utils.translation import ugettext as _ 
     8 
     9 
     10def quote(s): 
     11    """ 
     12    Ensure that primary key values do not confuse the admin URLs by escaping 
     13    any '/', '_' and ':' characters. Similar to urllib.quote, except that the 
     14    quoting is slightly different so that it doesn't get automatically 
     15    unquoted by the Web browser. 
     16    """ 
     17    if not isinstance(s, basestring): 
     18        return s 
     19    res = list(s) 
     20    for i in range(len(res)): 
     21        c = res[i] 
     22        if c in """:/_#?;@&=+$,"<>%\\""": 
     23            res[i] = '_%02X' % ord(c) 
     24    return ''.join(res) 
     25 
     26def unquote(s): 
     27    """ 
     28    Undo the effects of quote(). Based heavily on urllib.unquote(). 
     29    """ 
     30    mychr = chr 
     31    myatoi = int 
     32    list = s.split('_') 
     33    res = [list[0]] 
     34    myappend = res.append 
     35    del list[0] 
     36    for item in list: 
     37        if item[1:2]: 
     38            try: 
     39                myappend(mychr(myatoi(item[:2], 16)) + item[2:]) 
     40            except ValueError: 
     41                myappend('_' + item) 
     42        else: 
     43            myappend('_' + item) 
     44    return "".join(res) 
    845 
    946def _nest_help(obj, depth, val): 
  • django/branches/newforms-admin/django/contrib/admin/views/main.py

    r7881 r7935  
    11from django.contrib.admin.filterspecs import FilterSpec 
    22from django.contrib.admin.options import IncorrectLookupParameters 
     3from django.contrib.admin.util import quote 
    34from django.core.paginator import Paginator, InvalidPage 
    45from django.db import models 
     
    3031# Text to display within change-list table cells if the value is blank. 
    3132EMPTY_CHANGELIST_VALUE = '(None)' 
    32  
    33 def quote(s): 
    34     """ 
    35     Ensure that primary key values do not confuse the admin URLs by escaping 
    36     any '/', '_' and ':' characters. Similar to urllib.quote, except that the 
    37     quoting is slightly different so that it doesn't get automatically 
    38     unquoted by the Web browser. 
    39     """ 
    40     if type(s) != type(''): 
    41         return s 
    42     res = list(s) 
    43     for i in range(len(res)): 
    44         c = res[i] 
    45         if c in ':/_': 
    46             res[i] = '_%02X' % ord(c) 
    47     return ''.join(res) 
    4833 
    4934class ChangeList(object): 
  • django/branches/newforms-admin/django/db/models/base.py

    r7881 r7935  
    138138 
    139139    def add_to_class(cls, name, value): 
     140        if name == 'Admin': 
     141            import warnings 
     142            warnings.warn("The inner Admin class for %s is no longer supported. " 
     143                "Please use a ModelAdmin instead." % cls.__name__) 
    140144        if hasattr(value, 'contribute_to_class'): 
    141145            value.contribute_to_class(cls, name) 
  • django/branches/newforms-admin/tests/regressiontests/admin_views/models.py

    r7685 r7935  
    4949            } 
    5050        ) 
     51 
     52class ModelWithStringPrimaryKey(models.Model): 
     53    id = models.CharField(max_length=255, primary_key=True) 
     54     
     55    def __unicode__(self): 
     56        return self.id 
    5157         
    5258admin.site.register(Article, ArticleAdmin) 
    5359admin.site.register(CustomArticle, CustomArticleAdmin) 
    5460admin.site.register(Section) 
     61admin.site.register(ModelWithStringPrimaryKey) 
  • django/branches/newforms-admin/tests/regressiontests/admin_views/tests.py

    r7924 r7935  
    33from django.contrib.auth.models import User, Permission 
    44from django.contrib.contenttypes.models import ContentType 
     5from django.contrib.admin.models import LogEntry 
    56from django.contrib.admin.sites import LOGIN_FORM_KEY, _encode_post_data 
     7from django.contrib.admin.util import quote 
     8from django.utils.html import escape 
    69 
    710# local test models 
    8 from models import Article, CustomArticle, Section 
     11from models import Article, CustomArticle, Section, ModelWithStringPrimaryKey 
    912 
    1013def get_perm(Model, perm): 
     
    319322        self.failUnlessEqual(Article.objects.all().count(), 0) 
    320323        self.client.get('/test_admin/admin/logout/') 
     324 
     325class AdminViewStringPrimaryKeyTest(TestCase): 
     326    fixtures = ['admin-views-users.xml', 'string-primary-key.xml'] 
     327     
     328    def __init__(self, *args): 
     329        super(AdminViewStringPrimaryKeyTest, self).__init__(*args) 
     330        self.pk = """abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 -_.!~*'() ;/?:@&=+$, <>#%" {}|\^[]`""" 
     331     
     332    def setUp(self): 
     333        self.client.login(username='super', password='secret') 
     334        content_type_pk = ContentType.objects.get_for_model(ModelWithStringPrimaryKey).pk 
     335        LogEntry.objects.log_action(100, content_type_pk, self.pk, self.pk, 2, change_message='') 
     336     
     337    def tearDown(self): 
     338        self.client.logout() 
     339     
     340    def test_get_change_view(self): 
     341        "Retrieving the object using urlencoded form of primary key should work" 
     342        response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(self.pk)) 
     343        self.assertContains(response, escape(self.pk)) 
     344        self.failUnlessEqual(response.status_code, 200) 
     345     
     346    def test_changelist_to_changeform_link(self): 
     347        "The link from the changelist referring to the changeform of the object should be quoted" 
     348        response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/') 
     349        should_contain = """<tr class="row1"><th><a href="%s/">%s</a></th></tr>""" % (quote(self.pk), escape(self.pk)) 
     350        self.assertContains(response, should_contain) 
     351     
     352    def test_recentactions_link(self): 
     353        "The link from the recent actions list referring to the changeform of the object should be quoted" 
     354        response = self.client.get('/test_admin/admin/') 
     355        should_contain = """<a href="admin_views/modelwithstringprimarykey/%s/">%s</a>""" % (quote(self.pk), escape(self.pk)) 
     356        self.assertContains(response, should_contain) 
     357     
     358    def test_deleteconfirmation_link(self): 
     359        "The link from the delete confirmation page referring back to the changeform of the object should be quoted" 
     360        response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/delete/' % quote(self.pk)) 
     361        should_contain = """<a href="../../%s/">%s</a>""" % (quote(self.pk), escape(self.pk)) 
     362        self.assertContains(response, should_contain)