Lazy hyperlink names (#4554)

This commit is contained in:
Tom Christie 2016-10-11 12:18:00 +01:00 committed by GitHub
parent d0b3b6470a
commit 7f29cfc931
3 changed files with 61 additions and 8 deletions

View File

@ -37,14 +37,21 @@ class Hyperlink(six.text_type):
We use this for hyperlinked URLs that may render as a named link We use this for hyperlinked URLs that may render as a named link
in some contexts, or render as a plain URL in others. in some contexts, or render as a plain URL in others.
""" """
def __new__(self, url, name): def __new__(self, url, obj):
ret = six.text_type.__new__(self, url) ret = six.text_type.__new__(self, url)
ret.name = name ret.obj = obj
return ret return ret
def __getnewargs__(self): def __getnewargs__(self):
return(str(self), self.name,) return(str(self), self.name,)
@property
def name(self):
# This ensures that we only called `__str__` lazily,
# as in some cases calling __str__ on a model instances *might*
# involve a database lookup.
return six.text_type(self.obj)
is_hyperlink = True is_hyperlink = True
@ -303,9 +310,6 @@ class HyperlinkedRelatedField(RelatedField):
kwargs = {self.lookup_url_kwarg: lookup_value} kwargs = {self.lookup_url_kwarg: lookup_value}
return self.reverse(view_name, kwargs=kwargs, request=request, format=format) return self.reverse(view_name, kwargs=kwargs, request=request, format=format)
def get_name(self, obj):
return six.text_type(obj)
def to_internal_value(self, data): def to_internal_value(self, data):
request = self.context.get('request', None) request = self.context.get('request', None)
try: try:
@ -384,8 +388,7 @@ class HyperlinkedRelatedField(RelatedField):
if url is None: if url is None:
return None return None
name = self.get_name(value) return Hyperlink(url, value)
return Hyperlink(url, name)
class HyperlinkedIdentityField(HyperlinkedRelatedField): class HyperlinkedIdentityField(HyperlinkedRelatedField):

View File

@ -135,7 +135,8 @@ def add_class(value, css_class):
@register.filter @register.filter
def format_value(value): def format_value(value):
if getattr(value, 'is_hyperlink', False): if getattr(value, 'is_hyperlink', False):
return mark_safe('<a href=%s>%s</a>' % (value, escape(value.name))) name = six.text_type(value.obj)
return mark_safe('<a href=%s>%s</a>' % (value, escape(name)))
if value is None or isinstance(value, bool): if value is None or isinstance(value, bool):
return mark_safe('<code>%s</code>' % {True: 'true', False: 'false', None: 'null'}[value]) return mark_safe('<code>%s</code>' % {True: 'true', False: 'false', None: 'null'}[value])
elif isinstance(value, list): elif isinstance(value, list):

View File

@ -0,0 +1,49 @@
from django.conf.urls import url
from django.db import models
from django.test import TestCase, override_settings
from rest_framework import serializers
from rest_framework.renderers import JSONRenderer
from rest_framework.templatetags.rest_framework import format_value
str_called = False
class Example(models.Model):
text = models.CharField(max_length=100)
def __str__(self):
global str_called
str_called = True
return 'An example'
class ExampleSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Example
fields = ('url', 'id', 'text')
def dummy_view(request):
pass
urlpatterns = [
url(r'^example/(?P<pk>[0-9]+)/$', dummy_view, name='example-detail'),
]
@override_settings(ROOT_URLCONF='tests.test_lazy_hyperlinks')
class TestLazyHyperlinkNames(TestCase):
def setUp(self):
self.example = Example.objects.create(text='foo')
def test_lazy_hyperlink_names(self):
global str_called
context = {'request': None}
serializer = ExampleSerializer(self.example, context=context)
JSONRenderer().render(serializer.data)
assert not str_called
hyperlink_string = format_value(serializer.data['url'])
assert hyperlink_string == '<a href=/example/1/>An example</a>'
assert str_called