From abb55a490964790a65ad5ef32397c6046d03d889 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 30 Dec 2010 13:52:46 +0000 Subject: [PATCH] Add styling and urlizing to html views of resources --- src/rest/resource.py | 10 +- src/rest/templates/emitter.html | 13 ++- src/rest/templatetags/__init__.py | 0 src/rest/templatetags/__init__.pyc | Bin 0 -> 163 bytes src/rest/templatetags/urlize_quoted_links.py | 96 ++++++++++++++++++ src/rest/templatetags/urlize_quoted_links.pyc | Bin 0 -> 4515 bytes src/testapp/urls.py | 9 +- src/testapp/views.py | 11 +- src/urls.py | 2 +- 9 files changed, 130 insertions(+), 11 deletions(-) create mode 100644 src/rest/templatetags/__init__.py create mode 100644 src/rest/templatetags/__init__.pyc create mode 100644 src/rest/templatetags/urlize_quoted_links.py create mode 100644 src/rest/templatetags/urlize_quoted_links.pyc diff --git a/src/rest/resource.py b/src/rest/resource.py index 18421a19c..4e9c4e053 100644 --- a/src/rest/resource.py +++ b/src/rest/resource.py @@ -1,6 +1,8 @@ from django.http import HttpResponse -from decimal import Decimal +from django.core.urlresolvers import reverse from rest import emitters, parsers +from decimal import Decimal + class Resource(object): @@ -29,6 +31,7 @@ class Resource(object): def __new__(cls, request, *args, **kwargs): self = object.__new__(cls) self.__init__() + self._request = request return self._handle_request(request, *args, **kwargs) def __init__(self): @@ -145,3 +148,8 @@ class Resource(object): def delete(self, headers={}, *args, **kwargs): return self._not_implemented('delete') + + def reverse(self, view, *args, **kwargs): + """Return a fully qualified URI for a view, using the current request as the base URI. + """ + return self._request.build_absolute_uri(reverse(view, *args, **kwargs)) diff --git a/src/rest/templates/emitter.html b/src/rest/templates/emitter.html index 4c843aa34..b3a2d8234 100644 --- a/src/rest/templates/emitter.html +++ b/src/rest/templates/emitter.html @@ -1,11 +1,18 @@ - +{% load urlize_quoted_links %} + + +

{{ resource_name }}

{{ resource_doc }}

-
-{% include 'emitter.txt' %}    
+
{% autoescape off %}HTTP Status {{ status }}
+{% for key, val in headers.items %}{{ key }}: {{ val }}
+{% endfor %}
+{{ content|urlize_quoted_links }}{% endautoescape %}    
\ No newline at end of file diff --git a/src/rest/templatetags/__init__.py b/src/rest/templatetags/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/rest/templatetags/__init__.pyc b/src/rest/templatetags/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..69527f63db4bb8a1a773f526ee26c45266f761f4 GIT binary patch literal 163 zcmcckiI?kgy^L=%0~9a', '\n', '>', '"', "'"] + +# List of possible strings used for bullets in bulleted lists. +DOTS = ['·', '*', '\xe2\x80\xa2', '•', '•', '•'] + +unencoded_ampersands_re = re.compile(r'&(?!(\w+|#\d+);)') +word_split_re = re.compile(r'(\s+)') +punctuation_re = re.compile('^(?P(?:%s)*)(?P.*?)(?P(?:%s)*)$' % \ + ('|'.join([re.escape(x) for x in LEADING_PUNCTUATION]), + '|'.join([re.escape(x) for x in TRAILING_PUNCTUATION]))) +simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$') +link_target_attribute_re = re.compile(r'(]*?)target=[^\s>]+') +html_gunk_re = re.compile(r'(?:
|<\/i>|<\/b>|<\/em>|<\/strong>|<\/?smallcaps>|<\/?uppercase>)', re.IGNORECASE) +hard_coded_bullets_re = re.compile(r'((?:

(?:%s).*?[a-zA-Z].*?

\s*)+)' % '|'.join([re.escape(x) for x in DOTS]), re.DOTALL) +trailing_empty_content_re = re.compile(r'(?:

(?: |\s|
)*?

\s*)+\Z') + +def urlize_quoted_links(text, trim_url_limit=None, nofollow=False, autoescape=False): + """ + Converts any URLs in text into clickable links. + + Works on http://, https://, www. links and links ending in .org, .net or + .com. Links can have trailing punctuation (periods, commas, close-parens) + and leading punctuation (opening parens) and it'll still do the right + thing. + + If trim_url_limit is not None, the URLs in link text longer than this limit + will truncated to trim_url_limit-3 characters and appended with an elipsis. + + If nofollow is True, the URLs in link text will get a rel="nofollow" + attribute. + + If autoescape is True, the link text and URLs will get autoescaped. + """ + trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x + safe_input = isinstance(text, SafeData) + words = word_split_re.split(force_unicode(text)) + nofollow_attr = nofollow and ' rel="nofollow"' or '' + for i, word in enumerate(words): + match = None + if '.' in word or '@' in word or ':' in word: + match = punctuation_re.match(word) + if match: + lead, middle, trail = match.groups() + # Make URL we want to point to. + url = None + if middle.startswith('http://') or middle.startswith('https://'): + url = urlquote(middle, safe='/&=:;#?+*') + elif middle.startswith('www.') or ('@' not in middle and \ + middle and middle[0] in string.ascii_letters + string.digits and \ + (middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))): + url = urlquote('http://%s' % middle, safe='/&=:;#?+*') + elif '@' in middle and not ':' in middle and simple_email_re.match(middle): + url = 'mailto:%s' % middle + nofollow_attr = '' + # Make link. + if url: + trimmed = trim_url(middle) + if autoescape and not safe_input: + lead, trail = escape(lead), escape(trail) + url, trimmed = escape(url), escape(trimmed) + middle = '
%s' % (url, nofollow_attr, trimmed) + words[i] = mark_safe('%s%s%s' % (lead, middle, trail)) + else: + if safe_input: + words[i] = mark_safe(word) + elif autoescape: + words[i] = escape(word) + elif safe_input: + words[i] = mark_safe(word) + elif autoescape: + words[i] = escape(word) + return u''.join(words) + +# Register urlize_quoted_links as a custom filter +# http://docs.djangoproject.com/en/dev/howto/custom-template-tags/ +register = template.Library() +register.filter(urlize_quoted_links) \ No newline at end of file diff --git a/src/rest/templatetags/urlize_quoted_links.pyc b/src/rest/templatetags/urlize_quoted_links.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b49e16b67ab6eb018194b20a1a86719fba7b5a09 GIT binary patch literal 4515 zcmcgvOH&)!6+SH<0wEqYAmit>!GHiGjXje|#$aK}_Dp$-;EBNHDk0p}bYDb6t6T1S zHP}<4Dpj7WvdlW0{EGa5ERtpZKoVN)cIQOSN z-ukUar=M{=pWv^v86uykhS4HFLq`_XFiGbbs%2!}Lq|On_EN2v!al0?k;NFjbkt8_ zmTK7)?xUjt3J0k+n8N*Zl%sHnYC|cUrK3EB!&DmvJV5>+`8o23$j?&)Tt~b?8zW^=0OafR0lHu9f;R*l?}9hUzeT|W zHHdm^lcYxI*=~Vq1yTUOV2D(KDXzZ#FYQe7>;nEZQln@1050LrTRYbne?LP7)?lhD z^0sJjy$jwE@J1K>OeQz!1B`sDyJ%mOdYjbPnN$Fmr2g635&i{U25WFhIl>ZmMG|;r zNKNz4pQfLBs5V1tn1Xxs0rx#iYLiTa^ALMqkJa}q9Z+@%WWS7+UB3HA`^ISXq#B5_u8Oa4x*3f=k4Rb90-Pb z=c$$n;s}sy?3@mwMyPBz@@+qE?_nykwCyI!ZmFah7)Z8VZHLPBfo#W#Z4x(tE)BcY zjy%(LnO`Xv{12baBHWwI0ER7hUp;e;YX$){-Q>v8SSeb(Ka62NPCVtbqri*((`6>Z zkwt|Ru^%f+dv5CjhEdH{=o)31RZ+%O-HZ8LXjTNAmT_Ljl8pCdT#&J%nJ|}UJoxhKm2|q-4#O3lmDS%oeE8Lh9^-j&?QXGtywsVg z`%4Qe3%U=B#kyWv&|hNqrnvU~D%-D8TzfRD7Zw+Ifdz)DQd(RSP>gzpa8ESt=s6~@ zZ!JC9ahHEwU#@+={B_A$-d(DfK3ce^ud~cm*WP(k*#%GICJkjC@4Tt&%I=chS%gmCkBVS*@1?O!j!Phly(P3}a_in1w%Esq4iB7^ZltkRZ-Qd%CsKscWc_*!6N{;R0^G z=JD;aq4_$V;;;V!H|t2+{K=w|UNQjTR~EJT?XCwV$S$_4Kc>f|Yage()a#z2w60}RInFdbr)G-rRX zEKo#$Kf@XlDkoJcPls8GL)na#Jdok98!#Lu9!KaJ&Jcf>k|VJX3%Pv;k6cs&`=g=+ z&I~0`8lXqvk|Jwog2DDdYK>Hjd!QK;YvhH;1U9b-0~{c=xtM2!;y*w<>??RDqtxl4 zxPqlV%Ew=U3#jCjJP~~}Y(N|cu;|MZQ8WYAV8DD)!LZrM^3Hw_6<~0KqBhRFAPJ0k z6&A*ocLr$n?O!u2F@~DJOKiK_vz`IC^its%{|BBVDU^22UhG-q^n=*f_id0gU4DkKR?98ak4P^FH!d#S>T#iB*u}V|A}J#&vM3jr`7pG#2{8I; zRZD`#fe}vT0O+R&ez6aYf~JFdfx?J7#T7A%jlCI1>b?}auI>21sR2V|Gt^boJ}!VD z5K7^F%o0t4iiXnSOGUlJe)$XAJ8%=%Lt*BtK~Y5Bi+tcpbp=Ni1}z=va|NS#KMuqA zm{r?O+CQt7$VRrdT{}_XEb@q)j47_jFchMJ;b+r*mo^SuncpEJV~g*Uiv-D_Ut?B&rD6Os zI;67rd&~7y**|onM!cLTZI<^Fx2cX13T2&mWnL@W_ZqA14@r%KWCa()n|-N+K{&Z9{7y6CCqxHTt;T5TgH zVIyhLxy!+E5tefQfm}0VR6k?!tOMseIZ)c=2@mP;#NFYBXc>Tt4AWHpFXZL?3i^ov zxQAu^9R|9Rw??f=Yr-0)ywz{bTjN&Nn#}ZDwpFlhThrF()(vaX%2~Ng)?(c2SRJ?S z0-6Bk6#nV6I1g@eLdWrA&vDR?5#idVdHouOG-yk0#;{2?p07W9vH7j@{j1HV+ppHQU%cEj95CC} z^%ol-Z}8ndd%3+OheL$60(dVUs2z!ZcuRAesEHc+SF@wu#9md*!KBX4gWW0%6(@0a z9`07}Fv1)!O<~{!?xd}|IGE&p4l$127vFBatUiCbzV#eU9npblhEAgm0_>;Uzu1){ zLc{fq4Z|ix$?_e3!}1+|cR_MtY<^ehzQrM^TIM})B7W&)xVJ%Gk}=Z{rP$HPs~y{k?fBpD9>l>9=Rny3;e28HJ+` T!gU|v$?pA)HJZ7)lDGa3)|fTU literal 0 HcmV?d00001 diff --git a/src/testapp/urls.py b/src/testapp/urls.py index a41c156bc..a7d430bc3 100644 --- a/src/testapp/urls.py +++ b/src/testapp/urls.py @@ -1,8 +1,7 @@ from django.conf.urls.defaults import patterns -from testapp.views import ReadOnlyResource, MirroringWriteResource - -urlpatterns = patterns('', - (r'^read-only$', ReadOnlyResource), - (r'^mirroring-write$', MirroringWriteResource), +urlpatterns = patterns('testapp.views', + (r'^$', 'RootResource'), + (r'^read-only$', 'ReadOnlyResource'), + (r'^mirroring-write$', 'MirroringWriteResource'), ) diff --git a/src/testapp/views.py b/src/testapp/views.py index f0174414b..eca4c0ae8 100644 --- a/src/testapp/views.py +++ b/src/testapp/views.py @@ -1,6 +1,15 @@ -from decimal import Decimal from rest.resource import Resource +class RootResource(Resource): + """This is my docstring + """ + allowed_methods = ('GET',) + + def read(self, headers={}, *args, **kwargs): + return (200, {'read-only-api': self.reverse(ReadOnlyResource), + 'write-only-api': self.reverse(MirroringWriteResource)}, {}) + + class ReadOnlyResource(Resource): """This is my docstring """ diff --git a/src/urls.py b/src/urls.py index f95e9afa4..41a32efa9 100644 --- a/src/urls.py +++ b/src/urls.py @@ -5,7 +5,7 @@ admin.autodiscover() urlpatterns = patterns('', # Example: - (r'^testapp/', include('testapp.urls')), + (r'^', include('testapp.urls')), # Uncomment the admin/doc line below to enable admin documentation: (r'^admin/doc/', include('django.contrib.admindocs.urls')),