1 """
2 Renderers that take services "as arguments".
3 """
4
5
6
7
8
9
10
11 import cgi
12 import datetime
13 import re
14 import urllib
15
16 from nevow import inevow
17 from nevow import loaders
18 from nevow import rend
19 from nevow import tags as T, entities as E
20 from nevow import url
21 from twisted.web import http
22
23 from gavo import base
24 from gavo import registry
25 from gavo import svcs
26 from gavo import utils
27 from gavo.protocols import creds
28 from gavo.registry.model import OAI
29 from gavo.web import common
30 from gavo.web import grend
34 """returns tx in a way that hopefully prevents larger disasters
35 when used with BibTeX.
36
37 Among others, this just looks for multiple uppercase characters within
38 one word and protects the respective word with curly braces; for now,
39 this is ASCII only.
40
41 This is also where we escape for TeX.
42 """
43 return re.sub(r"([#$_&\\%])", r"\\\1",
44 re.sub(r"(\w*[A-Z]\w+[A-Z]\w*)", r"{\1}", tx))
45
92
140
149
151 """returns BibTeX for the current record.
152
153 This will return None if no BibTeX can be made.
154 """
155 try:
156 return makeBibTeXForMetaCarrier(self.metaCarrier)
157 except base.MetaError:
158 return None
159
162 """is a container for various functions having to do with explaining
163 renderers on services.
164
165 Use the explain(renderer, service) class method.
166 """
167
168 @classmethod
172
173 @classmethod
175 return T.invisible["a ",
176 T.a(href=service.getURL("fixed"))["custom page"],
177 ", possibly with dynamic content"]
178
179 @classmethod
181 return T.invisible["a ",
182 T.a(href=service.getURL("volatile"))["custom page"],
183 ", possibly with dynamic content"]
184
185 @classmethod
187
188 def generateArguments():
189
190 fieldIter = iter(service.getInputKeysFor("soap"))
191 try:
192 next = fieldIter.next()
193 while True:
194 desc = "%s/%s"%(next.name, next.type)
195 if next.required:
196 desc = T.strong[desc]
197 yield desc
198 next = fieldIter.next()
199 yield ', '
200 except StopIteration:
201 pass
202
203 return T.invisible["enables remote procedure calls; to use it,"
204 " feed the WSDL URL "+
205 service.getURL("soap")+"/go?wsdl"+
206 " to your SOAP library; the function signature is"
207 " useService(",
208 generateArguments(),
209 "). See also our ",
210 T.a(render=T.directive("rootlink"), href="/static/doc/soaplocal.shtml")[
211 "local soap hints"]]
212
213 @classmethod
215 res = T.invisible["a custom rendering of the service, typically"
216 " for interactive web applications."]
217 if svcs.getRenderer("custom").isBrowseable(service):
218 res[" See also the ",
219 T.a(href=service.getURL("custom"))["entry page"], "."]
220 return res
221
222 @classmethod
224 return T.invisible["static (i.e. prepared) data or custom client-side"
225 " code; probably used to access ancillary files here"]
226
227
228 @classmethod
229 - def _explain_text(cls, service):
230 return T.invisible["a text interface not intended for user"
231 " applications"]
232
233 @classmethod
235 return T.invisible["a standard SIAP interface as defined by the"
236 " IVOA to access collections of celestial images; SIAP clients"
237 " use ", service.getURL("siap.xml"), " to access the service",
238 T.invisible(render=T.directive("ifadmin"))[" -- ",
239 T.a(href="http://nvo.ncsa.uiuc.edu/dalvalidate/SIAValidater?endpoint="+
240 urllib.quote(service.getURL("siap.xml"))+
241 "&RA=%s&DEC=%s&RASIZE=%s&DECSIZE=%s&FORMAT=ALL&"
242 "format=html&show=fail&show=warn&show=rec&op=Validate"%(
243 base.getMetaText(service, "testQuery.pos.ra", default="180"),
244 base.getMetaText(service, "testQuery.pos.dec", default="60"),
245 base.getMetaText(service, "testQuery.size.ra", default="3"),
246 base.getMetaText(service, "testQuery.size.dec", default="3")))[
247 "Validate"]]]
248
249 @classmethod
251 return T.invisible["a standard SCS interface as defined by the"
252 " IVOA to access catalog-type data; SCS clients"
253 " use ", service.getURL("scs.xml"), " to access the service",
254 T.invisible(render=T.directive("ifadmin"))[" -- ",
255 T.a(href="http://nvo.ncsa.uiuc.edu/dalvalidate/"
256 "ConeSearchValidater?endpoint="+
257 urllib.quote(service.getURL("scs.xml"))+
258 "&RA=%s&DEC=%s&SR=%s&format=html&show=fail&show=warn&show=rec"
259 "&op=Validate"%(
260 base.getMetaText(service, "testQuery.ra", default="180"),
261 base.getMetaText(service, "testQuery.dec", default="60"),
262 base.getMetaText(service, "testQuery.sr", default="1")))[
263 "Validate"]]]
264
265 @classmethod
267 tqKeys = cgi.parse_qs(
268 base.getMetaText(service, "ssap.testQuery", default=""))
269
270 opts = ["batch=yes&"
271 "service=http%3A%2F%2Fvoparis-validator.obspm.fr%2Fxml%2F111.xml%3F"]
272 for standardKey, default in [
273 ("REQUEST", "queryData"),
274 ("SIZE", ""),
275 ("POS", ""),
276 ("TIME", ""),
277 ("BAND", ""),
278 ("FORMAT", "ALL")]:
279 opts.append("%s=%s"%(standardKey,
280 urllib.quote(tqKeys.pop(standardKey, [default])[0])))
281 opts.append("addparams="+urllib.quote("\n".join(
282 "%s=%s"%(k,urllib.quote(v[0])) for k,v in tqKeys.iteritems())))
283 optStr = "&".join(opts)
284 if optStr:
285 optStr = optStr+"&"
286
287 return T.invisible["a standard SSAP interface as defined by the"
288 " IVOA to access spectral or time-series data; SSAP clients"
289 " use ", service.getURL("ssap.xml"), " to access the service",
290 T.invisible(render=T.directive("ifadmin"))[" -- ",
291 T.a(href=
292 "http://voparis-validator.obspm.fr/validator.php?"
293 "spec=Simple+Spectral+Access+1.04&"
294 "format=XHTML&%sserviceURL=%s"%(
295 optStr,
296 urllib.quote(service.getURL("ssap.xml"))))[
297 "Validate"]]]
298
299 @classmethod
301 return T.invisible["a standard SLAP interface as defined by the"
302 " IVOA to access spectral line data; SLAP clients (usually"
303 " spectral analysis programs)"
304 " use ", service.getURL("slap.xml"), " to access the service"]
305
306 @classmethod
308 return T.invisible["the interface to this site's Table Access Protocol"
309 " service. This protocol is best accessed using specialized clients"
310 " or libraries. In such clients, you can find this service by its"
311 " IVOID, ",
312 T.code(render=T.directive("meta"))["identifier"],
313 ", or access it by entering its base URL ",
314 T.code[service.getURL("tap")],
315 " directly. Using an XSL-enabled web browser you can, in a pinch,"
316 " also operate ",
317 T.a(href=service.getURL("tap")+"/async")["the service"],
318 " without a specialized client."]
319
320 @classmethod
322 return T.invisible["a user-defined UWS service."
323 " This service is best accessed using specialized clients"
324 " or libraries. Give those its base URL ",
325 T.a(href=service.getURL("uws.xml"))[service.getURL("uws.xml")],
326 ". Using an XSL-enabled web browser you can"
327 " also click on the link above and operate the service 'manually'."
328 " For parameters and the output schema, see below."]
329
330 @classmethod
332 return T.invisible["an interface for the OAI-PMH protocol, typically"
333 " this site's publishing registry (but possibly some other"
334 " registry-like thing). This endpoint is usually accessed"
335 " by harvesters, but with an XML-enabled browser you can"
336 " also try the access URL at ",
337 T.a(href=service.getURL("pubreg.xml"))[service.getURL("pubreg.xml")],
338 "."]
339
340 @classmethod
342 return T.invisible["an interface that uses the last path element"
343 " to query the column %s in the underlying table."%
344 service.getProperty("queryField", "defunct")]
345
346 @classmethod
348 return T.invisible["a ",
349 T.a(href=service.getURL("upload"))["form-based interface"],
350 " for uploading data"]
351
352 @classmethod
354 return T.invisible["an upload interface for use with custom"
355 " upload programs. These should access ",
356 service.getURL("mupload")]
357
358 @classmethod
360 return T.invisible["a ",
361 T.a(href=service.getURL("img.jpeg"))["form-based interface"],
362 " to generate jpeg images from the underlying data"]
363
364 @classmethod
366 return T.invisible["an interface to image creation targeted at machines."
367 " The interface is at %s."%service.getURL("img.jpeg"),
368 " This is probably irrelevant to you."]
369
370 @classmethod
372 return T.invisible["a datalink interface letting specialized clients"
373 " retrieve parts of datasets or discover related data. You"
374 " use this kind of service exclusively in combination with"
375 " a pubdid, usually via a direct link."]
376
377 @classmethod
384
385 @classmethod
387 return T.invisible["an asynchronous interface to retrieving"
388 " processed data. This needs a special client that presumably"
389 " would first look at the dlmeta endpoint to discover what"
390 " processing options are available."]
391
392 @classmethod
394 return T.invisible["an interface for operation with curl and"
395 " similar low-level-tools. The endpoint is at ",
396 T.a(href=service.getURL("api"))[service.getURL("api")],
397 "; as usual for DALI-conforming services, parameters"
398 " an response structure is available by ",
399 T.a(href=service.getURL("api")+"MAXREC=0")["querying with"
400 " MAXREC=0"],
401 "."]
402
403 @classmethod
405 return T.invisible["an interface to retrieve the spatial coverage"
406 " of this service. By default, this will return a FITS MOC,"
407 " but browsers and similar clients declaring they accept PNGs"
408 " will get a sky plot showing the coverage."]
409
410 @classmethod
412 return T.invisible["a renderer with some custom access method that"
413 " should be mentioned in the service description"]
414
415 @classmethod
416 - def explain(cls, renderer, service):
419
422 """A renderer showing all kinds of metadata on a service.
423
424 This renderer produces the default referenceURL page. To change its
425 appearance, override the serviceinfo.html template.
426 """
427 name = "info"
428
429 customTemplate = svcs.loadSystemTemplate("serviceinfo.html")
430
435
439
441 if not data["note"]:
442 return ""
443 id = data["note"].tag
444 self.footnotes.add(data["note"])
445 return ctx.tag(href="#note-%s"%id)["Note %s"%id]
446
453
456
461
466
472
477
483
486
489
490 defaultDocFactory = common.doctypedStan(
491 T.html[
492 T.head[
493 T.title["Missing Template"]],
494 T.body[
495 T.p["Infos are only available with a serviceinfo.html template"]]
496 ])
497
500 """A renderer for displaying table information.
501
502 Since tables don't necessarily have associated services, this
503 renderer cannot use a service to sit on. Instead, the table is
504 being passed in as as an argument. There's a built-in vanity tableinfo
505 that sits on //dc_tables#show using this renderer (it could really
506 sit anywhere else).
507 """
508 name = "tableinfo"
509 customTemplate = svcs.loadSystemTemplate("tableinfo.html")
510
519
527
530
532 res = [f.asInfoDict() for f in self.table]
533 for d in res:
534 if d["note"]:
535 d["noteKey"] = d["note"].tag
536 if "alphaOrder" in inevow.IRequest(ctx).args:
537 res.sort(key=lambda item: item["name"].lower())
538 return res
539
542
544 return ctx.tag["Table information for '%s'"%self.tableName]
545
555
561
563 """renders the content if there was a tapinfo key somewhere in
564 the query string.
565 """
566 if "tapinfo" in inevow.IRequest(ctx).args:
567 return ctx.tag
568 else:
569 return ""
570
573
575 if len(segments)!=1:
576 return None, ()
577 self._retrieveTableDef(segments[0])
578 return self, ()
579
580 defaultDocFactory = common.doctypedStan(
581 T.html[
582 T.head[
583 T.title["Missing Template"]],
584 T.body[
585 T.p["Infos are only available with a tableinfo.html template"]]
586 ])
587
590 """A renderer for displaying table notes.
591
592 It takes a schema-qualified table name and a note tag in the segments.
593
594 This does not use the underlying service, so it could and will run on
595 any service. However, you really should run it on __system__/dc_tables/show,
596 and there's a built-in vanity name tablenote for this.
597 """
598 name = "tablenote"
599
606
617
619 if len(segments)==2:
620 self._retrieveNote(segments[0], segments[1])
621 elif len(segments)==3:
622
623 self._retrieveNote(segments[1], segments[2])
624 self.docFactory = self.innerDocFactory
625 else:
626 return None, ()
627 return self, ()
628
631
634
636 return T.xml(self.noteHTML)
637
638 docFactory = common.doctypedStan(T.html[
639 T.head[
640 T.title["%s -- Note for table "%base.getConfig("web", "sitename"),
641 T.invisible(render=rend.data, data=T.directive("tableName"))],
642 T.invisible(render=T.directive("commonhead")),
643 T.style["span.target {font-size: 180%;font-weight:bold}"],
644 ],
645 T.body[
646 T.invisible(render=T.directive("noteHTML"))]])
647
648 innerDocFactory = loaders.stan(
649 T.invisible(render=T.directive("noteHTML")))
650
658
661 """A renderer returning various forms of a service's spatial coverage.
662
663 This will return a 404 if the service doesn't have a coverage.spatial
664 meta (and will bomb out if that isn't a SMoc).
665
666 Based on the accept header, it will return a PNG if the client
667 indicates it's interested in that or if it accepts text/html, in which
668 case we assume it's a browser; otherwise, it will produce a
669 MOC in FITS format.
670 """
671 name = "coverage"
672
691
692 @classmethod
694 """returns true if request indicates we're being retrieved by
695 a web browser.
696 """
697 acceptDict = utils.parseAccept(request.getHeader("accept"))
698 return ("image/png" in acceptDict
699 or "image/*" in acceptDict
700 or "text/html" in acceptDict)
701
702 @classmethod
704 """only cache this if we return a PNG.
705
706 Our caching system doesn't support content negotiation, so we
707 can't keep the FITS (and it's fast to generate, so that doesn't
708 matter so much.
709 """
710 return cls.returnAPNG(request)
711
714 """A renderer representing a (tutorial-like) text document.
715
716 Not sure yet what I'll do when people actually call this; for now,
717 the access URL must be given as metadata.
718 """
719 name = "edition"
720
723 """A renderer redirecting to an external resource.
724
725 These try to access an external publication on the parent service
726 and ask it for an accessURL. If it doesn't define one, this will
727 lead to a redirect loop.
728
729 In the DC, external renderers are mainly used for registration of
730 third-party browser-based services.
731 """
732 name = "external"
733
734 @classmethod
737
747
748
749 -class RDInfoPage(grend.CustomTemplateMixin, grend.ResourceBasedPage):
750 """A page giving infos about an RD.
751
752 This is not a renderer but a helper for RDInfoRenderer.
753 """
754 customTemplate = svcs.loadSystemTemplate("rdinfo.html")
755
756 - def data_services(self, ctx, data):
757 return sorted(self.rd.services,
758 key=lambda s: base.getMetaText(s, "title", default=s.id))
759
760 - def data_tables(self, ctx, data):
761 return sorted((t for t in self.rd.tables
762 if t.onDisk
763 and not t.temporary
764 and not t.hasProperty("internal")),
765 key=lambda t: t.id)
766
767 - def data_clientRdId(self, ctx, data):
768 return self.rd.sourceId
769
770 - def _getDescriptionHTML(self, descItem):
771 """returns stan for the "description" of a service or a table.
772
773 The RD's description is not picked up.
774 """
775 iDesc = descItem.getMeta("description", propagate=False)
776 if iDesc is None:
777 return ""
778 else:
779 return T.div(class_="lidescription")[
780 T.xml(iDesc.getContent("blockhtml", macroPackage=descItem))]
781
782 - def render_rdsvc(self, ctx, service):
783 return ctx.tag[
784 T.a(href=service.getURL("info"))[
785 base.getMetaText(service, "title", default=service.id)],
786 self._getDescriptionHTML(service)]
787
788 - def render_rdtable(self, ctx, tableDef):
789 qName = tableDef.getQName()
790
791 adqlNote = ""
792 if tableDef.adql:
793 adqlNote = T.span(class_="adqlnote")[" ",
794 E.ndash, " queriable through ",
795 T.a(href="/tap")["TAP"], " and ",
796 T.a(href="/adql")["ADQL"],
797 " "]
798
799 return ctx.tag[
800 T.a(href="/tableinfo/%s"%qName)[qName],
801 adqlNote,
802 self._getDescriptionHTML(tableDef)]
803
804 @classmethod
805 - def makePageTitle(cls, rd):
806 """returns a suitable title for the rd info page.
807
808 This is a class method to allow other renderers to generate
809 titles for link anchors.
810 """
811 return "Information on resource '%s'"%base.getMetaText(
812 rd, "title", default="%s"%rd.sourceId)
813
814 - def render_title(self, ctx, data):
815 return ctx.tag[self.makePageTitle(self.rd)]
816
817 defaultDocFactory = common.doctypedStan(
818 T.html[
819 T.head[
820 T.title["Missing Template"]],
821 T.body[
822 T.p["RD infos are only available with an rdinfo.html template"]]
823 ])
824
825
826 -class RDInfoRenderer(grend.CustomTemplateMixin, grend.ServiceBasedPage):
827 """A renderer for displaying various properties about a resource descriptor.
828
829 This renderer could really be attached to any service since
830 it does not call it, but it usually lives on //services/overview.
831
832 By virtue of builtin vanity, you can reach the rdinfo renderer
833 at /browse, and thus you can access /browse/foo/q to view the RD infos.
834 This is the form used by table registrations.
835
836 In addition to all services, this renderer also links tableinfos
837 for all non-temporary, on-disk tables defined in the RD. When
838 you actually want to hide some internal on-disk tables, you can
839 set a property ``internal`` on the table (the value is ignored).
840 """
841 name = "rdinfo"
842 customTemplate = svcs.loadSystemTemplate("rdlist.html")
843
845 with base.getTableConn() as conn:
846 return [row[0] for row in
847 conn.query(
848 """SELECT DISTINCT sourceRD
849 FROM (
850 SELECT sourceRD FROM dc.resources
851 WHERE NOT deleted) as q
852 ORDER BY sourceRD""")]
853
860
861 defaultDocFactory = common.doctypedStan(
862 T.html[
863 T.head[
864 T.title["Missing Template"]],
865 T.body[
866 T.p["The RD list is only available with an rdlist.html template"]]
867 ])
868
871 """logs users out.
872
873 With a valid authorization header, this emits a 401 unauthorized,
874 without one, it displays a logout page.
875 """
876 name = "logout"
877
878 openRenderer = True
879
888
889 defaultDocFactory = common.doctypedStan(
890 T.html[
891 T.head[
892 T.title["Logged out"]],
893 T.body[
894 T.h1["Logged out"],
895 T.p["Your browser no longer has valid credentials for this site."
896 " Close this window or continue at the ",
897 T.a(href=base.makeAbsoluteURL("/"))["root page"],
898 "."]]])
899
902 """A page that returns resource records for internal services.
903
904 This is basically like OAI-PMH getRecord, except we're using rd/id/svcid
905 from our path.
906
907 Also (and that's fairly important for purx), this will use the RD's
908 datetimeUpdated meta for a modified header.
909 """
912
941