1 """
2 Basic Code for Renderers.
3
4 Renderers are frontends for services. They provide the glue to
5 somehow acquire input (typically, nevow contexts) and then format
6 the result for the user.
7 """
8
9
10
11
12
13
14
15 import os
16
17 from nevow import tags as T
18 from nevow import loaders
19 from nevow import inevow
20 from nevow import rend
21 from nevow import url
22
23 from twisted.internet import threads
24 from twisted.python import log
25 from zope.interface import implements
26
27 from gavo import base
28 from gavo import svcs
29 from gavo import rsc
30 from gavo.protocols import creds
31 from gavo.web import common
32 from gavo.web import htmltable
33
34
35 __docformat__ = "restructuredtext en"
39 """is raised when a ResourceDescriptor is blocked due to maintanence
40 and caught by the root resource..
41 """
42
47 """A mixin with renderers useful throughout the data center.
48
49 Rendering of meta information:
50
51 * <tag n:render="meta">METAKEY</tag> or
52 * <tag n:render="metahtml">METAKEY</tag>
53
54 Rendering the sidebar --
55 <body n:render="withsidebar">. This will only work if the renderer
56 has a service attribute that's enough of a service (i.e., carries meta
57 and knows how to generate URLs).
58
59 Conditional rendering:
60
61 * ifmeta
62 * imownmeta
63 * ifdata
64 * ifnodata
65 * ifslot
66 * ifnoslot
67 * ifadmin
68
69 Obtaining system info
70
71 * rd <rdId> -- makes the referenced RD the current data (this is
72 not too useful right now, but it lets you check of the existence
73 of RDs already)
74 """
75 _sidebar = svcs.loadSystemTemplate("sidebar.html")
76 _footer = svcs.loadSystemTemplate("footer.html")
77
78
79
80
81 macroPackage = None
82
87
125
131 return get
132
138
144
150
163
169
171 if data:
172 return ctx.tag
173 else:
174 return ""
175
177 if not data:
178 return ctx.tag
179 else:
180 return ""
181
183 """renders the children for slotName is present and true.
184
185 This will not work properly if the slot values come from a deferred.
186 """
187 def render(ctx, data):
188 try:
189 if ctx.locateSlotData(slotName):
190 return ctx.tag
191 else:
192 return ""
193 except KeyError:
194 return ""
195 return render
196
198 """renders if slotName is missing or not true.
199
200 This will not work properly if the slot values come from a deferred.
201 """
202
203
204 def render(ctx, data):
205 try:
206 if not ctx.locateSlotData(slotName):
207 return ctx.tag
208 else:
209 return ""
210 except KeyError:
211 return ""
212 return render
213
215
216
217
218
219 if inevow.IRequest(ctx).getUser()=="gavoadmin":
220 return ctx.tag
221 else:
222 return ""
223
234
236 """returns something suitable for inclusion above the form.
237
238 The renderer tries, in sequence, to retrieve a meta called _intro,
239 the description meta, or nothing.
240 """
241 for key in ["_intro", "description"]:
242 if self.service.getMeta(key, default=None) is not None:
243 introKey = key
244 break
245 else:
246 introKey = None
247 if introKey is None:
248 return ctx.tag[""]
249 else:
250 return ctx.tag[T.xml(self.metaCarrier.buildRepr(introKey,
251 common.HTMLMetaBuilder(self.macroPackage),
252 raiseOnFail=False))]
253
255 request = inevow.IRequest(ctx)
256 svc = getattr(self, "service", None)
257
258 if svc and request.getUser():
259 anchorText = "Log out %s"%request.getUser()
260 targetURL = svc.getURL("logout", False)
261 explanation = " (give an empty user name in the dialog popping up)"
262 else:
263 targetURL = url.URL.fromString("/login").add("nextURL",
264 str(url.URL.fromContext(ctx)))
265 anchorText = "Log in"
266 explanation = ""
267
268 return ctx.tag[T.a(href=str(targetURL))[
269 anchorText], explanation]
270
272 """prepends a site id to the body.
273
274 This is intended for titles and similar; it puts the string in
275 [web]sitename in front of anything that already is in ctx.tag.
276 """
277 ctx.tag.children = [base.getConfig("web", "sitename")]+ctx.tag.children
278 return ctx.tag
279
291
293 """returns the RD referenced in the body (or None if the RD is not there)
294 """
295 try:
296 return base.caches.getRD(rdId)
297 except base.NotFoundError:
298 return None
299
302 """is a mixin with render functions for HTML tables and associated
303 metadata within other pages.
304
305 This is primarily used for the Form renderer.
306 """
307 result = None
308
321
330
332 if data is None or data[1] is None or "__" in data[0]:
333 return ""
334 return ctx.tag["%s: %s"%data]
335
337 if self.result.queryMeta.get("Matched", 1)!=0:
338 return ctx.tag
339 else:
340 return ""
341
343 if self.result.queryMeta.get("Matched", 1)==0:
344 return ctx.tag
345 else:
346 return ""
347
349 """renders ctx.tag if we have a linkable result, nothing otherwise.
350
351 Linkable means that the result will come out as displayed through
352 a link. Currently, we only see if a file upload was part of
353 the result production -- if there was, it's not linkable.
354
355 This currently doesn't even look if a file was indeed passed in: Things
356 already are not linkable if the service takes a file upload, whether
357 that's used or not.
358 """
359 for ik in self.service.getInputKeysFor(self):
360 if ik.type=='file':
361 return ""
362 return ctx.tag
363
365 """enters custom service styles into ctx.tag.
366
367 They are taken from the service's customCSS property.
368 """
369 if self.service and self.service.getProperty("customCSS", False):
370 return ctx.tag[self.service.getProperty("customCSS")]
371 return ""
372
375
385
386 __suppressedParNames = set(["submit"])
387
389 if not self.result:
390 return []
391
392 if self.service:
393 fieldDict = dict((f.name, f)
394 for f in self.service.getInputKeysFor(self))
395 else:
396 fieldDict = {}
397
398 s = [self._makeParPair(k, v, fieldDict)
399 for k, v in self.result.queryMeta.get("formal_data", {}).iteritems()
400 if v is not None and v!=[]
401 and k not in self.__suppressedParNames
402 and not k.startswith("_")]
403 s.sort()
404 return s
405
407 """adds an onClick attribute opening a flot plot.
408
409 This is evaluates the _plotOptions meta. This should be a javascript
410 dictionary literal with certain plot options. More on this in
411 the reference documentation on the _plotOptions meta.
412 """
413 plotOptions = base.getMetaText(self.service, "_plotOptions")
414 if plotOptions is not None:
415 args = ", %s"%plotOptions
416 else:
417 args = ""
418 return ctx.tag(onclick="openFlotPlot($('table.results')%s)"%args)
419
421 """returns the value of the data.getParam(content) formatted as a python
422 string.
423
424 Undefined params and NULLs give N/A.
425 """
426 def renderer(ctx, data):
427 parName = ctx.tag.children[0].strip()
428 ctx.tag.clear()
429 try:
430 val = data.getParam(parName)
431 if val is None:
432 return ctx.tag["N/A"]
433
434 return ctx.tag[format%val]
435 except base.NotFoundError:
436 return ctx.tag["N/A"]
437 return renderer
438
441 """a mixin providing for customized templates.
442
443 This works by making docFactory a property first checking if
444 the instance has a customTemplate attribute evaluating to true.
445 If it has and it is referring to a string, its content is used
446 as a resdir-relative path to a nevow XML template. If it has and
447 it is not a string, it will be used as a template directly
448 (it's already "loaded"), else defaultDocFactory attribute of
449 the instance is used.
450 """
451 customTemplate = None
452
463
464 docFactory = property(getDocFactory)
465
466
467
468
469
470
471 -class GavoPage(rend.Page, GavoRenderMixin):
472 """a base class for all "pages" (i.e. things talking to the web) within
473 DaCHS.
474 """
475
476
477 -class ResourceBasedPage(GavoPage):
478 """A base for renderers based on RDs.
479
480 It is constructed with the resource descriptor and leaves it
481 in the rd attribute.
482
483 The preferredMethod attribute is used for generation of registry records
484 and currently should be either GET or POST. urlUse should be one
485 of full, base, post, or dir, in accord with VOResource.
486
487 Renderers with fixed result types should fill out resultType.
488
489 The makeAccessURL class method is called by service.getURL; it
490 receives the service's base URL and must return a mogrified string
491 that corresponds to an endpoint this renderer will operate on (this
492 could be used to make a Form renderer into a ParamHTTP interface by
493 attaching ?__nevow_form__=genForm&, and the soap renderer does
494 nontrivial things there).
495
496 Within DaCHS, this class is mainly used as a base for ServiceBasedRenderer,
497 since almost always only services talk to the world. However,
498 we try to fudge render and data functions such that the sidebar works.
499 """
500 implements(inevow.IResource)
501
502 preferredMethod = "GET"
503 urlUse = "full"
504 resultType = None
505
506
507
508 parameterStyle = "clear"
509 name = None
510
511 - def __init__(self, ctx, rd):
512 rend.Page.__init__(self)
513 self.rd = rd
514 self.metaCarrier = rd
515 self.macroPackage = rd
516 if hasattr(self.rd, "currently_blocked"):
517 raise RDBlocked()
518 self._initGavoRender()
519
520 @classmethod
521 - def isBrowseable(self, service):
522 """returns True if this renderer applied to service is usable using a
523 plain web browser.
524 """
525 return False
526
527 @classmethod
528 - def isCacheable(self, segments, request):
529 """should return true if the content rendered will only change
530 when the associated RD changes.
531
532 request is a nevow request object. web.root.ArchiveService already
533 makes sure that you only see GET request without arguments and
534 without a user, so you do not need to check this.
535 """
536 return False
537
538 @classmethod
539 - def makeAccessURL(cls, baseURL):
540 """returns an accessURL for a service with baseURL to this renderer.
541 """
542 return "%s/%s"%(baseURL, cls.name)
543
544 - def data_rdId(self, ctx, data):
545 return self.rd.sourceId
546
547 - def data_serviceURL(self, type):
548
549 return lambda ctx, data: base.makeSitePath("/browse/%s"%self.rd.sourceId)
550
551
552 _IGNORED_KEYS = set(["__nevow_form__", "_charset_", "submit", "nextURL"])
583
584
585 -class ServiceBasedPage(ResourceBasedPage):
586 """the base class for renderers turning service-based info into
587 character streams.
588
589 You will need to provide some way to give rend.Page nevow templates,
590 either by supplying a docFactory or (usually preferably) mixing in
591 CustomTemplateMixin -- or just override renderHTTP to make do
592 without templates.
593
594 The class overrides nevow's child and render methods to allow the
595 service to define render_X and data_X methods, too.
596
597 You can set an attribute checkedRenderer=False for renderers that
598 are "generic" and do not need to be enumerated in the allowed
599 attribute of the underlying service ("meta renderers").
600
601 You can set a class attribute openRenderer=True to make a renderer
602 work even on restricted services (which may make sense for stuff like
603 logout and maybe for metadata inspection).
604 """
605
606 checkedRenderer = True
607 openRenderer = False
608
609 - def __init__(self, ctx, service):
610 ResourceBasedPage.__init__(self, ctx, service.rd)
611
612 self.service = service
613 request = inevow.IRequest(ctx)
614 if not self.openRenderer and service.limitTo:
615 if not creds.hasCredentials(request.getUser(), request.getPassword(),
616 service.limitTo):
617 raise svcs.Authenticate()
618
619 if self.checkedRenderer and self.name not in self.service.allowed:
620 raise svcs.ForbiddenURI(
621 "The renderer %s is not allowed on this service."%self.name,
622 rd=self.service.rd)
623
624
625
626
627
628 self.HANDLING_HTTPS = request.isSecure()
629
630 self.metaCarrier = self.service
631 self.macroPackage = self.service
632
633
634 self.fieldsChanged = False
635
636 self._logRequestArgs(request)
637 self._fillServiceDefaults(request.args)
638
639 - def _logRequestArgs(self, request):
640 """leaves the actual arguments of a request in the log.
641 """
642 try:
643 if request.args:
644
645
646 fmtArgs = _formatRequestArgs(request.args)
647 if fmtArgs!='{}':
648 log.msg("# Processing starts: %s %s"%(request.path,
649 fmtArgs))
650 except:
651 base.ui.notifyError("Formatting of request args failed.")
652
653 - def _fillServiceDefaults(self, args):
654 """a hook to enter default parameters based on the service.
655 """
656 if self.service.core.hasProperty("defaultSortKey"):
657 if "_DBOPTIONS_ORDER" not in args:
658 args["_DBOPTIONS_ORDER"] = self.service.core.getProperty(
659 "defaultSortKey").split(",")
660
661 - def processData(self, rawData, queryMeta=None):
662 """calls the actual service.
663
664 This will run in the current thread; you will ususally
665 want to use runService from the main nevow event loop unless you know
666 the service is quick or actually works asynchronously.
667 """
668 return self.service.run(self, rawData, queryMeta)
669
670 - def runService(self, rawData, queryMeta=None):
671 """takes raw data and returns a deferred firing the service result.
672
673 This will process everything in a thread.
674 """
675 return threads.deferToThread(self.processData, rawData, queryMeta)
676
678 """runs the service, taking arguments from material preparsed
679 by nevow formal.
680
681 This is the entry point for the form renderer and its friends.
682 """
683 if queryMeta is None:
684 queryMeta = svcs.QueryMeta.fromContext(context)
685 queryMeta["formal_data"] = rawData
686
687
688
689
690 if (self.service.core.outputTable.columns and
691 not self.service.getCurOutputFields(queryMeta)):
692 raise base.ValidationError("These output settings yield no"
693 " output fields", "_OUTPUT")
694
695 data = dict((k, [v] if v is not None else None)
696 for k,v in rawData.iteritems())
697 return self.runService(svcs.PreparsedInput(data), queryMeta)
698
699 - def data_serviceURL(self, renderer):
700 """returns a relative URL for this service using the renderer.
701
702 This is ususally used like this:
703
704 <a><n:attr name="href" n:data="serviceURL info" n:render="data">x</a>
705 """
706 def get(ctx, data):
707 return self.service.getURL(renderer, absolute="False")
708 return get
709
710 - def renderer(self, ctx, name):
711 """returns a nevow render function named name.
712
713 This overrides the method inherited from nevow's RenderFactory to
714 add a lookup in the page's service service.
715 """
716 if name in self.service.nevowRenderers:
717 return self.service.nevowRenderers[name]
718 return rend.Page.renderer(self, ctx, name)
719
720 - def child(self, ctx, name):
721 """returns a nevow data function named name.
722
723 In addition to nevow's action, this also looks methods up in the
724 service.
725 """
726 if name in self.service.nevowDataFunctions:
727 return self.service.nevowDataFunctions[name]
728 return rend.Page.child(self, ctx, name)
729
730 - def renderHTTP(self, ctx):
731 return rend.Page.renderHTTP(self, ctx)
732
733 - def locateChild(self, ctx, segments):
734
735
736
737 if segments==("",):
738 raise svcs.WebRedirect(url.URL.fromContext(ctx))
739 else:
740 res = ResourceBasedPage.locateChild(self, ctx, segments)
741 if res[0] is None:
742 raise svcs.UnknownURI("%s has no child resources"%repr(self.name))
743 return res
744
745
746 if __name__=="__main__":
747 import doctest, grend
748 doctest.testmod(grend)
749