#29839: Make leading character in quote/unquote in contrib.admin.utils
configurable
-------------------------------------+-------------------------------------
Reporter: Chris Z. | Owner: nobody
Type: New feature | Status: new
Component: contrib.admin | Version: 2.1
Severity: Normal | Resolution:
Keywords: admin quote unquote | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Description changed by Chris Z.:
Old description:
> = **The problem:**
>
> The current leading character in the {{{contrib.admin.utils}}}
> {{{quote()}}} and {{{unquote()}}} functions (an underscore) causes some
> characters to be improperly escaped/unescaped if primary keys contain an
> underscore. This character could be configurable through a setting in
> {{{settings.py}}}, so that users who have underscores in text-based
> primary keys can change it. If there is a primary key with an underscore
> in it, for example, changing the leading character in the quote/unquote
> functions to a dot ({{{.}}}) will fix edit links in the django admin.
>
> = **The current (django 2.1.1) functions, with suggested changes:**
>
> {{{
> def quote(s):
> """
> Ensure that primary key values do not confuse the admin URLs by
> escaping
> any '/', '_' and ':' and similarly problematic characters.
> Similar to urllib.parse.quote(), except that the quoting is slightly
> different so that it doesn't get automatically unquoted by the Web
> browser.
> """
> if not isinstance(s, str):
> return s
> res = list(s)
> for i in range(len(res)):
> c = res[i]
> if c in """:/_#?;@&=+$,"[]<>%\n\\""":
> res[i] = '_%02X' % ord(c) # Make this underscore a
> configurable character
> return ''.join(res)
>
> def unquote(s):
> """Undo the effects of quote(). Based heavily on
> urllib.parse.unquote()."""
> mychr = chr
> myatoi = int
> list = s.split('_') # Make this underscore a configurable character
> res = [list[0]]
> myappend = res.append
> del list[0]
> for item in list:
> if item[1:2]:
> try:
> myappend(mychr(myatoi(item[:2], 16)) + item[2:])
> except ValueError:
> myappend('_' + item) # Make this underscore a
> configurable character
> else:
> myappend('_' + item) # Make this underscore a configurable
> character
> return "".join(res)
> }}}
>
> = **A couple example models:**
>
> {{{
> from django.db import models
> from django.contrib.auth.models import User
>
> # CUSTOMERS:
> class Customer(models.Model):
> user = models.OneToOneField(User, on_delete=models.CASCADE)
> customer_id = models.CharField(max_length=100, primary_key=True)
>
> def __str__(self):
> return self.customer_id
>
> # SUBSCRIPTIONS:
> class Subscription(models.Model):
>
> subscription_id = models.CharField(max_length=100, primary_key=True)
> customer_id = models.ForeignKey('Customer', db_column='customer_id',
> on_delete=models.CASCADE)
>
> def __str__(self):
> return self.subscription_id
> }}}
>
> = **Steps to reproduce the issue:**
>
> 1. Create the above models.
> 2. Create a {{{Customer}}} object with a primary key of
> {{{cus_C2testing}}}
> 3. Create a Subscription object with a primary key of {{{sub_C2testing}}}
> and a ForeignKey back to the object created in step 1
> 4. Navigate to the django admin and click the Subscription object you
> created. **Notice that the underscore in the primary key has been
> replaced with {{{_5F}}} in the URL**
> 5. Click the edit customer icon (the small pencil next to the dropdown
> containing your customer object) and notice that the popup displays
> {{{Customer with ID "cusÂtesting" doesn't exist. Perhaps it was
> deleted?}}}. The C2 has been replaced with its unicode codepoint, Â. I
> believe this is due to the fact that the unquote function splits the ID
> at the underscore, but regardless of the cause, **replacing the
> underscore in the quote/unquote functions at the locations in the code
> above with a dot ({{{.}}}) resolves the issue.**
>
> = **The suggestion:**
> My suggestion is that we make this configurable, so that the
> quote/unquote functions use an underscore by default, but take a second
> argument of a leading character if desired. It would ideally be
> configurable via settings.py in a variable like
> {{{ADMIN_QUOTE_UNQUOTE_LEADCHAR}}}. This will give users the flexibility
> to use primary keys with underscores, which are likely to be much more
> common than keys with dots.
New description:
= **Important Note:**
It's worth noting before reading all of this that apparently backporting
these two commits will actually eliminate the need for this feature:
https://github.com/django/django/commit/e9defb3f6e60b626e9ec40ff5df1322fceb52601
https://github.com/django/django/commit/e4df8e6dc021fa472fa77f9b835db74810184748
= **The problem:**
The current leading character in the {{{contrib.admin.utils}}}
{{{quote()}}} and {{{unquote()}}} functions (an underscore) causes some
characters to be improperly escaped/unescaped if primary keys contain an
underscore. This character could be configurable through a setting in
{{{settings.py}}}, so that users who have underscores in text-based
primary keys can change it. If there is a primary key with an underscore
in it, for example, changing the leading character in the quote/unquote
functions to a dot ({{{.}}}) will fix edit links in the django admin.
= **The current (django 2.1.1) functions, with suggested changes:**
{{{
def quote(s):
"""
Ensure that primary key values do not confuse the admin URLs by
escaping
any '/', '_' and ':' and similarly problematic characters.
Similar to urllib.parse.quote(), except that the quoting is slightly
different so that it doesn't get automatically unquoted by the Web
browser.
"""
if not isinstance(s, str):
return s
res = list(s)
for i in range(len(res)):
c = res[i]
if c in """:/_#?;@&=+$,"[]<>%\n\\""":
res[i] = '_%02X' % ord(c) # Make this underscore a
configurable character
return ''.join(res)
def unquote(s):
"""Undo the effects of quote(). Based heavily on
urllib.parse.unquote()."""
mychr = chr
myatoi = int
list = s.split('_') # Make this underscore a configurable character
res = [list[0]]
myappend = res.append
del list[0]
for item in list:
if item[1:2]:
try:
myappend(mychr(myatoi(item[:2], 16)) + item[2:])
except ValueError:
myappend('_' + item) # Make this underscore a configurable
character
else:
myappend('_' + item) # Make this underscore a configurable
character
return "".join(res)
}}}
= **A couple example models:**
{{{
from django.db import models
from django.contrib.auth.models import User
# CUSTOMERS:
class Customer(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
customer_id = models.CharField(max_length=100, primary_key=True)
def __str__(self):
return self.customer_id
# SUBSCRIPTIONS:
class Subscription(models.Model):
subscription_id = models.CharField(max_length=100, primary_key=True)
customer_id = models.ForeignKey('Customer', db_column='customer_id',
on_delete=models.CASCADE)
def __str__(self):
return self.subscription_id
}}}
= **Steps to reproduce the issue:**
1. Create the above models.
2. Create a {{{Customer}}} object with a primary key of
{{{cus_C2testing}}}
3. Create a Subscription object with a primary key of {{{sub_C2testing}}}
and a ForeignKey back to the object created in step 1
4. Navigate to the django admin and click the Subscription object you
created. **Notice that the underscore in the primary key has been replaced
with {{{_5F}}} in the URL**
5. Click the edit customer icon (the small pencil next to the dropdown
containing your customer object) and notice that the popup displays
{{{Customer with ID "cusÂtesting" doesn't exist. Perhaps it was
deleted?}}}. The C2 has been replaced with its unicode codepoint, Â. I
believe this is due to the fact that the unquote function splits the ID at
the underscore, but regardless of the cause, **replacing the underscore in
the quote/unquote functions at the locations in the code above with a dot
({{{.}}}) resolves the issue.**
= **The suggestion:**
My suggestion is that we make this configurable, so that the quote/unquote
functions use an underscore by default, but take a second argument of a
leading character if desired. It would ideally be configurable via
settings.py in a variable like {{{ADMIN_QUOTE_UNQUOTE_LEADCHAR}}}. This
will give users the flexibility to use primary keys with underscores,
which are likely to be much more common than keys with dots.
--
--
Ticket URL: <https://code.djangoproject.com/ticket/29839#comment:1>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/065.9598062dab03c8f93e20ad9b413ad6b8%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.