Package gavo :: Package web :: Module metarender
[frames] | no frames]

Source Code for Module gavo.web.metarender

  1  """ 
  2  Renderers that take services "as arguments". 
  3  """ 
  4   
  5  #c Copyright 2008-2019, the GAVO project 
  6  #c 
  7  #c This program is free software, covered by the GNU GPL.  See the 
  8  #c COPYING file in the source distribution. 
  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 
31 32 33 -def _protectForBibTeX(tx):
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
46 47 -def makeBibTeXForMetaCarrier(mc):
48 """returns a (unicode) best-effort BibTeX record for something mixing in 49 mc. 50 51 This needs, at least, a creationDate, a creator, a referenceURL, 52 and a title to work; if these aren't there, you'll get a MetaError. 53 """ 54 creationDate = utils.parseISODT( 55 base.getMetaText(mc, 56 "creationDate", 57 raiseOnFail=True)) 58 year = creationDate.year 59 60 # for the BibTeX tag, prefer the short name if given... 61 label = base.getMetaText(mc, "shortName", None) 62 if label is None: 63 # ... else hope it's a table... 64 try: 65 label = mc.getQName() 66 except AttributeError: 67 # fall back to something made from the creation date 68 # (which at least won't change between invocations) 69 label = utils.intToFunnyWord( 70 int(re.sub("[^\d]+", "", creationDate.isoformat()))) 71 label = "vo:"+re.sub("[^\w]", "_", label) 72 73 authors = " and ".join(m.getContent() 74 for m in mc.iterMeta("creator.name", 75 propagate=True)) 76 77 res = [ 78 "@MISC{%s"%label, 79 " year=%s"%year, 80 " title={%s}"%_protectForBibTeX( 81 base.getMetaText(mc, "title", "Untitled Resource")), 82 " author={%s}"%_protectForBibTeX(authors), 83 " url={%s}"%base.getMetaText(mc, "referenceURL", raiseOnFail=True), 84 " howpublished={{VO} resource provided by the %s}"% 85 _protectForBibTeX(base.getConfig("web", "sitename")), 86 ] 87 doi = base.getMetaText(mc, "doi", None) 88 if doi: 89 res.append( 90 " doi = {%s}"%doi) 91 return u",\n".join(res)+"\n}\n"
92
93 94 -class MetaRenderer(grend.CustomTemplateMixin, grend.ServiceBasedPage):
95 """Renderers that are allowed on all services. 96 """ 97 checkedRenderer = False 98 99 @classmethod
100 - def isCacheable(self, segments, request):
101 return True
102
103 - def data_otherServices(self, ctx, data):
104 """returns a list of dicts describing other services provided by the 105 the describing RD. 106 107 The class mixing this in needs to provide a describingRD attribute for 108 this to work. This may be the same as self.service.rd, and the 109 current service will be excluded from the list in this case. 110 """ 111 res = [] 112 for svc in self.describingRD.services: 113 if svc is not self.service: 114 res.append({"infoURL": svc.getURL("info"), 115 "title": base.getMetaText(svc, "title")}) 116 return res
117
118 - def render_sortOrder(self, ctx, data):
119 request = inevow.IRequest(ctx) 120 if "alphaOrder" in request.args: 121 return ctx.tag["Sorted alphabetically. ", 122 T.a(href=url.URL.fromRequest(request).remove("alphaOrder"))[ 123 "[Sort by DB column index]"]] 124 else: 125 return ctx.tag["Sorted by DB column index. ", 126 T.a(href=url.URL.fromRequest(request).add("alphaOrder", "True"))[ 127 "[Sort alphabetically]"]]
128 133
134 - def render_ifkey(self, keyName):
135 def render(ctx, data): 136 if keyName in data: 137 return ctx.tag 138 return ""
139 return render
140
141 - def render_ifbibcode(self, ctx, data):
142 """renders its children if the source metadata looks like a bibcode. 143 """ 144 source = base.getMetaText(self.metaCarrier, "source", "") 145 if utils.couldBeABibcode(source): 146 return ctx.tag 147 else: 148 return ""
149
150 - def data_bibtexentry(self, ctx, data):
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
160 161 -class RendExplainer(object):
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
169 - def _explain_form(cls, service):
170 return T.invisible["allows access via an ", 171 T.a(href=service.getURL("form"))["HTML form"]]
172 173 @classmethod
174 - def _explain_fixed(cls, service):
175 return T.invisible["a ", 176 T.a(href=service.getURL("fixed"))["custom page"], 177 ", possibly with dynamic content"]
178 179 @classmethod
180 - def _explain_volatile(cls, service):
181 return T.invisible["a ", 182 T.a(href=service.getURL("volatile"))["custom page"], 183 ", possibly with dynamic content"]
184 185 @classmethod
186 - def _explain_soap(cls, service):
187 188 def generateArguments(): 189 # Slightly obfuscated -- I need to get ", " in between the items. 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
214 - def _explain_custom(cls, service):
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
223 - def _explain_static(cls, service):
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
234 - def _explain_siap_xml(cls, service):
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
250 - def _explain_scs_xml(cls, service):
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
266 - def _explain_ssap_xml(cls, service):
267 tqKeys = cgi.parse_qs( 268 base.getMetaText(service, "ssap.testQuery", default="")) 269 # validator php seems to insist on that key 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
300 - def _explain_slap_xml(cls, service):
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
307 - def _explain_tap(cls, service):
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
321 - def _explain_uws_xml(cls, service):
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
331 - def _explain_pubreg_xml(cls, service):
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
341 - def _explain_qp(cls, service):
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
347 - def _explain_upload(cls, service):
348 return T.invisible["a ", 349 T.a(href=service.getURL("upload"))["form-based interface"], 350 " for uploading data"]
351 352 @classmethod
353 - def _explain_mupload(cls, service):
354 return T.invisible["an upload interface for use with custom" 355 " upload programs. These should access ", 356 service.getURL("mupload")]
357 358 @classmethod
359 - def _explain_img_jpeg(cls, service):
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
365 - def _explain_mimg_jpeg(cls, service):
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
371 - def _explain_dlget(cls, service):
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
378 - def _explain_dlmeta(cls, service):
379 return T.invisible["a datalink interface for discovering access" 380 " options (processed data, related datasets...) for a dataset." 381 " You usually need a publisherDID to use this kind of service." 382 " For special applications, the base URL of this service might" 383 " still come handy: %s"%service.getURL("dlmeta")]
384 385 @classmethod
386 - def _explain_dlasync(cls, service):
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
393 - def _explain_api(cls, service):
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
404 - def _explain_coverage(cls, renderer, service):
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
411 - def _explainEverything(cls, service):
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):
417 return getattr(cls, "_explain_"+renderer.replace(".", "_"), 418 cls._explainEverything)(service)
419
420 421 -class ServiceInfoRenderer(MetaRenderer, utils.IdManagerMixin):
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
431 - def __init__(self, *args, **kwargs):
432 grend.ServiceBasedPage.__init__(self, *args, **kwargs) 433 self.describingRD = self.service.rd 434 self.footnotes = set()
435
436 - def render_title(self, ctx, data):
437 return ctx.tag["Information on Service '%s'"% 438 base.getMetaText(self.service, "title")]
439
440 - def render_notebubble(self, ctx, data):
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
447 - def render_footnotes(self, ctx, data):
448 """renders the footnotes as a definition list. 449 """ 450 return T.dl(class_="footnotes")[[ 451 T.xml(note.getContent(targetFormat="html")) 452 for note in sorted(self.footnotes, key=lambda n: n.tag)]]
453
454 - def data_internalpath(self, ctx, data):
455 return "%s/%s"%(self.service.rd.sourceId, self.service.id)
456
457 - def data_inputFields(self, ctx, data):
458 res = [f.asInfoDict() for f in self.service.getInputKeysFor("info")] 459 res.sort(key=lambda val: val["name"].lower()) 460 return res
461
462 - def data_htmlOutputFields(self, ctx, data):
463 res = [f.asInfoDict() for f in self.service.getCurOutputFields()] 464 res.sort(key=lambda val: val["name"].lower()) 465 return res
466
467 - def data_votableOutputFields(self, ctx, data):
468 queryMeta = svcs.QueryMeta({"_FORMAT": "VOTable", "_VERB": 3}) 469 res = [f.asInfoDict() for f in self.service.getCurOutputFields(queryMeta)] 470 res.sort(key=lambda val: val["verbLevel"]) 471 return res
472
473 - def data_rendAvail(self, ctx, data):
474 return [{"rendName": rend, 475 "rendExpl": RendExplainer.explain(rend, self.service)} 476 for rend in self.service.allowed]
477
478 - def data_publications(self, ctx, data):
479 res = [{"sets": ",".join(p.sets), "render": p.render} 480 for p in self.service.publications 481 if p.sets and p.render not in registry.HIDDEN_RENDERERS] 482 return sorted(res, key=lambda v: v["render"])
483
484 - def data_browserURL(self, ctx, data):
485 return self.service.getBrowserURL()
486
487 - def data_service(self, ctx, data):
488 return self.service
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
498 499 -class TableInfoRenderer(MetaRenderer):
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
511 - def renderHTTP(self, ctx):
512 if not hasattr(self, "table"): 513 # _retrieveTableDef did not run, i.e., no tableName was given 514 raise svcs.UnknownURI( 515 "You must provide a table name to this renderer.") 516 self.macroPackage = self.table 517 self.metaCarrier = self.table 518 return super(TableInfoRenderer, self).renderHTTP(ctx)
519
520 - def _retrieveTableDef(self, tableName):
521 try: 522 self.tableName = tableName 523 self.table = registry.getTableDef(tableName) 524 self.describingRD = self.table.rd 525 except base.NotFoundError as msg: 526 raise base.ui.logOldExc(svcs.UnknownURI(str(msg)))
527
528 - def data_forADQL(self, ctx, data):
529 return self.table.adql
530
531 - def data_fields(self, ctx, data):
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
540 - def data_internalpath(self, ctx, data):
541 return "%s/%s"%(self.table.rd.sourceId, self.table.id)
542
543 - def render_title(self, ctx, data):
544 return ctx.tag["Table information for '%s'"%self.tableName]
545
546 - def render_rdmeta(self, ctx, data):
547 # rdmeta: Meta info at the table's rd (since there's ownmeta) 548 metaKey = ctx.tag.children[0] 549 ctx.tag.clear() 550 htmlBuilder = common.HTMLMetaBuilder(self.describingRD) 551 try: 552 return ctx.tag[T.xml(self.describingRD.buildRepr(metaKey, htmlBuilder))] 553 except base.NoMetaKey: 554 return ""
555
556 - def render_ifrdmeta(self, metaName):
557 if self.describingRD.getMeta(metaName, propagate=False): 558 return lambda ctx, data: ctx.tag 559 else: 560 return lambda ctx, data: ""
561
562 - def render_iftapinfo(self, ctx, data):
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
571 - def data_tableDef(self, ctx, data):
572 return self.table
573
574 - def locateChild(self, ctx, segments):
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
588 589 -class TableNoteRenderer(MetaRenderer):
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
600 - def renderHTTP(self, ctx):
601 if not hasattr(self, "noteTag"): 602 # _retrieveTableDef did not run, i.e., no tableName was given 603 raise svcs.UnknownURI( 604 "You must provide table name and note tag to this renderer.") 605 return super(TableNoteRenderer, self).renderHTTP(ctx)
606
607 - def _retrieveNote(self, tableName, noteTag):
608 try: 609 table = registry.getTableDef(tableName) 610 self.metaCarrier = table 611 self.noteHTML = table.getNote(noteTag 612 ).getContent(targetFormat="html", macroPackage=table) 613 except base.NotFoundError as msg: 614 raise base.ui.logOldExc(svcs.UnknownURI(msg)) 615 self.noteTag = noteTag 616 self.tableName = tableName
617
618 - def locateChild(self, ctx, segments):
619 if len(segments)==2: 620 self._retrieveNote(segments[0], segments[1]) 621 elif len(segments)==3: # segments[0] may be anything, 622 # but conventionally "inner" 623 self._retrieveNote(segments[1], segments[2]) 624 self.docFactory = self.innerDocFactory 625 else: 626 return None, () 627 return self, ()
628
629 - def data_tableName(self, ctx, data):
630 return self.tableName
631
632 - def data_noteTag(self, ctx, data):
633 return self.noteTag
634
635 - def render_noteHTML(self, ctx, data):
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
651 652 -class HowToCiteRenderer(MetaRenderer):
653 """A renderer that lets you format citation instructions. 654 """ 655 name = "howtocite" 656 657 customTemplate = svcs.loadSystemTemplate("howtocite.html")
658
659 660 -class CoverageRenderer(MetaRenderer):
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
673 - def renderHTTP(self, ctx):
674 from gavo.utils import pgsphere 675 try: 676 moc = pgsphere.SMoc.fromASCII( 677 self.service.getMeta( 678 "coverage.spatial", raiseOnFail=True)[0].getContent()) 679 except base.NoMetaKey: 680 raise svcs.UnknownURI( 681 "No spatial coverage available for this service.") 682 683 request = inevow.IRequest(ctx) 684 if self.returnAPNG(request): 685 request.setHeader("content-type", "image/png") 686 return moc.getPlot(xsize=400) 687 688 else: 689 request.setHeader("content-type", "application/fits") 690 return moc.asFITS()
691 692 @classmethod
693 - def returnAPNG(self, request):
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
703 - def isCacheable(cls, segments, request):
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
712 713 -class EditionRenderer(MetaRenderer):
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
721 722 -class ExternalRenderer(grend.ServiceBasedPage):
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
735 - def isBrowseable(self, service):
736 return True # we probably need some way to say when that's wrong...
737
738 - def renderHTTP(self, ctx):
739 # look for a matching publication in the parent service... 740 for pub in self.service.publications: 741 if pub.render==self.name: 742 break 743 else: # no publication, 404 744 raise svcs.UnknownURI("No publication for an external service here.") 745 raise svcs.WebRedirect(base.getMetaText(pub, "accessURL", 746 macroPackage=self.service))
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
844 - def data_publishedRDs(self, ctx, data):
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
854 - def locateChild(self, ctx, segments):
855 rdId = "/".join(segments) 856 if not rdId: 857 raise svcs.WebRedirect("browse") 858 clientRD = base.caches.getRD(rdId) 859 return RDInfoPage(ctx, clientRD), ()
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
869 870 -class LogoutRenderer(MetaRenderer):
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
880 - def renderHTTP(self, ctx):
881 request = inevow.IRequest(ctx) 882 if creds.hasCredentials( 883 request.getUser(), request.getPassword(), None): 884 # valid credentials: Ask again to make the browser discard them 885 raise svcs.Authenticate() 886 else: 887 return MetaRenderer.renderHTTP(self, ctx)
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
900 901 -class ResourceRecordMaker(rend.Page):
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 """
910 - def renderHTTP(self, ctx):
911 raise svcs.UnknownURI("What resource record do you want?")
912
913 - def locateChild(self, ctx, segments):
914 from gavo.registry import builders 915 916 rdParts, svcId = segments[:-1], segments[-1] 917 rdId = "/".join(rdParts) 918 try: 919 rd = base.caches.getRD(rdId) 920 resob = rd.getById(svcId) 921 except base.NotFoundError: 922 raise svcs.UnknownURI("The resource %s#%s is unknown at this site."%( 923 rdId, svcId)) 924 925 # This doesn't go through locateResourceBasedChild and thus 926 # isn't covered by the caching there. 927 request = inevow.IRequest(ctx) 928 if request.setLastModified(rd.timestampUpdated 929 ) is http.CACHED: 930 return "", () 931 932 return common.TypedData( 933 utils.xmlrender(OAI.PMH[ 934 OAI.responseDate[datetime.datetime.utcnow().isoformat()], 935 OAI.metadata[ 936 builders.getVORMetadataElement(resob)]], 937 prolog="<?xml version='1.0'?>" 938 "<?xml-stylesheet href='/static/xsl/oai.xsl' type='text/xsl'?>", 939 ), 940 "application/xml"), ()
941