Argument passing and DTMLFile methods
Hi all, First of all, thanks to Dieter Maurer, Charles Riemann, Michel Bencur, and Joachim Werner for help on the last couple of headaches on this project! I am getting really close to releasing this sucker, but ... ;-) Next up is how to properly pass implicit arguments to a class-method created by DTMLFile. This probably requires some explanation, though ... I'm converting a prototype of the product which I developed through the web. The presentation layer is fairly large. In it, I have a few instances of explicit DTMLMethod calls, like this: <dtml-var expr="mydtmlmethod(_.None, _, explicitarg=explicitval)"> This syntax is based on the note in the back of the Zope Book on the equivalent explicit call to a DTMLMethod. E.g., it claims that: <dtml-var expr="mydtmlmethod(_.None, _)"> is equivalent to <dtml-var mydtmlmethod> And, in the "through-the-web" prototype, this is apparently true. (That is, I have verified it experimentally). However, I am now trying to make the prototype into a product, and I want these DTML methods to act similarly to their Python method counterparts (NOT python scripts -- I have converted the scripts to product class methods, which become object methods when an instance of the object is created in Zope). So far, I've used SQL(), ImageFile(), and DTMLFile() to successfully convert ZSQL methods, images, and DTML methods over to the same sort of usage as I have with Python: from Globals import ImageFile from Globals import DTMLFile from Products.ZSQLMethods.SQL import SQL dbc = database_connection_object # Defined elsewhere class MyClass(Folder): meta_type = "my_class" def python_method(self, ...): #... python code adapted from python script ... # container/context converted to self, etc. # Naturally, this works just fine, as this # this is the stuff that's well-documented pass zsql_method = SQL('zsql_method', '', dbc, args, zsql_code) # Works perfectly dtml_method = DTMLFile('dtml/mydtmlmethod', globals(), document_id='mydtmlmethod') # Works great for implicit DTML usages. Variables, # acquisition, etc, all seem to work. image_data = ImageFile('www/myimage', globals()) # A little weird, because this is the actual # image data, not the Zope image object, but # still very usable. Basically, I can't # use "tag" or "absolute_url", but I can # access it via a URL. I wasn't certain this would work, since this is different from containing these objects within the folder (which can be done either by manage_addMyClass() or MyClass.__init__()). However, so far, it works pretty well -- DTML methods are able to find each other and include subsets, pass implicit variables, etc. They can be accessed by URL: MyClassInstance/dtml_method and acquisition works on them as expected. They exist for all instances of the object, and if the methods are altered, I only have to restart Zope to propagate the change to all instances. (I.e. I don't have to delete and re-add the instances). Furthermore, they are hidden from the management interface, and are thus "read-only". Great! This is all exactly what I want -- I'm freezing the prototype into a fixed product. Changes now have to be made to the product source code to take effect. This greatly reduces the hazard of accidently screwing up the product when you're trying to administrate it. The idea is that only objects which the administrator would need to alter are made available through the web. In short, they have all the properties of ordinary python class methods. So I'm not sure if this meets "best practice standards", but it seems like a very natural solution. I was beginning to be really happy about how smooth this was all going -- I could even get the correct context in included DTML methods. "Wow!" I thought, "this Zope thing is really coming together..." (and "I might actually make that July 1st date") EXCEPT... :-( This only works if I use *implicit* dtml-var substitutions, like: <dtml-var mydtml_submethod> If I try to use the explicit form mentioned above, <dtml-var expr="mydtml_submethod(_.None, _)"> I lose my context! I can see variables passed by keywords, but not the implicit ones. They were visible in the implicit form, but not the explicit one. Ack! Ironic, isn't it? You'd think I might have to be *more* explicit in this situation. My guess is that I've somehow chosen the wrong thing to pass to this method (perhaps "_" doesn't mean anything in this context?). Presumeably, Zope is doing the right thing for implicit references, but I can't figure out what that is. (This is *after* "reading the source". Some of the source is quite readable, but the DTML publishing parts are pretty hairy stuff!). For one thing, I'm not exactly sure what the difference is between my DTMLFile object and a DTML Method or DTML Document (it seems like it can't actually be either of those, but something related). I have the frustrating feeling that a simple answer exists for this, but that I'm just not seeing it, so I'm hoping this will seem obvious to somebody. If there's no other way around it, I can probably work-around this with dtml-let and passing all variables implicitly, or by sticking huge lists of variables in as keywords, but these are ugly solutions, and it seems like they ought to be unnecessary. Thanks in advance for any ideas. We *are* very close to release if this conversion process can be made to work (the prototype, while basically working, is not up to production use for a lot of reasons -- we think it will significantly benefit from being converted into a Product). I suspect "July 1st" is out the question at this point, but maybe *next* Monday at this rate. ;-D I have some screenshots of my prototype version on our website, for the curious: http://www.anansispaceworks.com/Columns/News/A.2002.05.20 http://www.anansispaceworks.net/NaryaInfo/naryaintro.html Naturally, Narya will be open source (GPL), but I do feel it should have at least minimal functionality before trying to release it. Thanks, Terry -- ------------------------------------------------------ Terry Hancock hancock@anansispaceworks.com Anansi Spaceworks http://www.anansispaceworks.com P.O. Box 60583 Pasadena, CA 91116-6583 ------------------------------------------------------
Terry Hancock writes:
.... EXCEPT... :-(
This only works if I use *implicit* dtml-var substitutions, like:
<dtml-var mydtml_submethod>
If I try to use the explicit form mentioned above,
<dtml-var expr="mydtml_submethod(_.None, _)">
I lose my context! In a Python method context, this looks like
mydtml_submethod(self,self.REQUEST, keyword_parameters) Dieter
Dieter Maurer wrote:
Terry Hancock writes:
.... EXCEPT... :-(
This only works if I use *implicit* dtml-var substitutions, like:
<dtml-var mydtml_submethod>
If I try to use the explicit form mentioned above,
<dtml-var expr="mydtml_submethod(_.None, _)">
I lose my context! In a Python method context, this looks like
mydtml_submethod(self, self.REQUEST, keyword_parameters)
Okay, I guess this would work from a Python method (I haven't tried that yet -- though I suppose I could write a wrapper that way -- call the python method, then call the DTMLFile method -- but then I'd lose my context there too, I think, since Python doesn't support the implicit passing that DTML does).
From another DTMLFile method, though, as in:
<dtml-var expr="mydtml_submethod(self,self.REQUEST, keywordparm=keywordval)"> this gives the same unfortunate results I had with passing "_.None, _" (just tested it). For the "client", I've since tried "this, _.this, _.this(), context, 'context'". All seem to have the same effect (actually _.this() generates an error indicating that _.this isn't callable -- sort of a relief, since it proved I was actually having some effect!). I also tried changing the DTMLFile from a class method to an instance method. I.e. changing from: class my_class(Folder): mydtml_method = DTMLFile('dtml/mydtml_method', globals()) mydtml_submethod = DTMLFile('dtml/mydtml_submethod', globals()) to class my_class(Folder): def __init__(self): self.mydtml_method = DTMLFile('dtml/mydtml_method', globals()) self.mydtml_submethod = DTMLFile('dtml/mydtml_submethod', globals()) But this doesn't seem to work. I wonder if this actually makes any difference, since presumeably the class methods are inherited by the instance anyway. (I think it does mean I have to recreate the object to allow changes to take effect -- I'm not certain I verified this to be true, though I do re-create the object just to be sure). Also, I still don't really understand what I'm doing here -- for example, where exactly *is* the caller's namespace in this example? I've called an object method through the web: HTTP_REQUEST --> my_object.mydtml_method Then that method is calling another method on the same object: my_object.mydtml_method --> my_object.mydtml_submethod So, is the client "my_object"? Does this mean that the DTML names are in "my_object"'s __dict__ (or something equivalent to that?). I can't really refer to the object explicitly, since I would need to know the instance name. (I'm sure there's a round-about way to do it, if I think about it hard enough). The thing that baffles me, is that: <dtml-let keywordparm=keywordval> <dtml-var mydtml_submethod> </dtml-let> works just fine (I've tested it several times). I guess that'll be my work-around if I can't figure out how to do it "right", but the problem makes me think I don't understand what I'm doing and might well shoot myself in the foot. Also, it's clear that Zope somehow *can* do what I want, since it does it for the implicit case. I've been trying to sort out the DTML processing code, following the traceback, which is, as I said, pretty hairy to read. It DOES appear that a "DTMLFile method" is indeed a different beast than either "DTML Method" or "DTML Document", and an underdocumented one -- a search for "DTMLFile" on www.zope.org turns up depressingly few references, and most of those are to using it for manage_add<product>Form methods. I know that the "globals()" call in the DTMLFile() constructor call is needed to find the correct location of the file -- does it do anything else? Can one set the client in the constructor call (or am I doing that by making it a method of the class or the class instance?). I made a sample product for this problem, which is at the URL below. As is, it doesn't work -- you have to alter the commented-out lines in "dtml/test_form_dtml" to see the "correct" output (just look for the <dtml-comment> element -- it's pretty obvious actually): http://www.anansispaceworks.com/Download/TestProduct.tgz Any comments on this example would be appreciated.
http://www.dieter.handshake.de/pyprojects/zope/book/chap3.html
And thanks for the book reference, Dieter (earlier in the DTML arguments thread that preceded this) -- is that your manuscript for Wrox? It looks pretty good so far. Looking at the section in there on calling "DTML Objects", I notice that it says that for "DTML Methods" there is no client, but that the caller's names are passed in the REQUEST. Am I barking up the wrong tree then? Is it the REQUEST variable that I'm getting wrong? Oh, I just found out one more thing -- if I change the web REQUEST to: ...my_class/mydtml_method?implicitvar=override Then the value "override" will be passed to mydtml_submethod, and the call succeeds! In other words, passing "_" is passing the REQUEST but not the namespace within mydtml_method. Does *that* mean anything to anyone? Well, I'm still thrashing around quite a bit -- not sure what sort of problem I have yet, so any suggestions would be appreciated. Thanks! Terry -- ------------------------------------------------------ Terry Hancock hancock@anansispaceworks.com Anansi Spaceworks http://www.anansispaceworks.com P.O. Box 60583 Pasadena, CA 91116-6583 ------------------------------------------------------
Terry Hancock writes:
Dieter Maurer wrote:
In a Python method context, this looks like
mydtml_submethod(self, self.REQUEST, keyword_parameters)
Okay, I guess this would work from a Python method (I haven't tried that yet... Maybe, you should state concisely from what context you want to pass parameters to DTMLFile methods:
DTMLFile objects behave identically in this respect to DTML methods! * When called from ZPublisher (i.e. from the Web), ZPublisher provides the object, the DTML is called for, as "client" and "REQUEST" as "REQUEST". * When called from DTML or Page Templates, "None" is passed as "client" and a DTML namespace as "REQUEST". * When you call it from either a Python method in your product, from an External Method or from a Python Script, you must pass sensible values for the two positional parameters "client" and "REQUEST". Often this is "self" and "self.REQEUST" (respectively "context" and "context.REQUEST", in a Python script). Dieter
Dieter Maurer wrote:
Maybe, you should state concisely from what context you want to pass parameters to DTMLFile methods:
DTMLFile objects behave identically in this respect to DTML methods!
My problem is that they don't -- I've tested the same code both ways, and it works with "DTML Methods" but breaks with "DTMLFile Methods". If indeed they were the same, then I'd be using: [your 2nd case]
* When called from DTML or Page Templates, "None" is passed as "client" and a DTML namespace as "REQUEST".
This is what I was originally doing, i.e. the second step in: Web Request --> DTML Method #1 --> DTML Method #2 ^^^ If this call is of either the explicit form: # -- Contents of "method1.dtml" - explicit version <dtml-let key1=val1 key2=val2> <dtml-var expr="method2(_.None, _)"> </dtml-let> or the implicit form: # -- Contents of "method1.dtml" - implicit version <dtml-let key1=val1 key2=val2> <dtml-var method2> </dtml-let> then I can happily use key1 and key2 inside Method #2 -- it inherits #1's namespace. In fact, these two calls are exactly equivalent. NOW -- just save the contents of Methods #1 and #2 into files, and load them as either class or instance methods of a Zope object created in a product (I spelled this step out in my last post, so I'll omit it here), and try the same thing: Web Request --> DTMLFile Method #1 --> DTMLFile Method #2 ^^^ In this case, the "implicit" call works, but the "explicit" call breaks. In other words, an implicit call is NO LONGER EQUIVALENT to an explicit one passing "_.None, _". (This manifests as a KeyError traceback on key1 or key2 if used in Method#2). If they "should" act the same way, then "I've Found a Bug"(TM)! In which case, I'm collecting information to file a bug report as concisely as possible. If not, then I just don't understand the distinctions between "DTMLFile Methods" and "DTML Methods". Naturally, I can recode to use the implicit case (in fact, I've already done this for my particular project), but it seems like there must be an equivalent explicit call. Sorry about the length -- I actually am trying to be "concise" :-), I just can't think of a way to put this any more directly. Thanks, Terry -- ------------------------------------------------------ Terry Hancock hancock@anansispaceworks.com Anansi Spaceworks http://www.anansispaceworks.com P.O. Box 60583 Pasadena, CA 91116-6583 ------------------------------------------------------
Terry Hancock writes:
Dieter Maurer wrote:
Maybe, you should state concisely from what context you want to pass parameters to DTMLFile methods:
DTMLFile objects behave identically in this respect to DTML methods!
My problem is that they don't -- I've tested the same code both ways, and it works with "DTML Methods" but breaks with "DTMLFile Methods". If indeed they were the same, then I'd be using: You are right, I am wrong!
"DTMLFile" uses the modern "Bindings" infrastructure introduced with Python Scripts and subsequently used by PageTemplates and "DTMLFile". You can pass the DTML namespace in the following way: your_dtml_file_object(caller_namespace=_, more_keyword_parameters) Dieter
Dieter Maurer wrote:
"DTMLFile" uses the modern "Bindings" infrastructure introduced with Python Scripts and subsequently used by PageTemplates and "DTMLFile".
You can pass the DTML namespace in the following way:
your_dtml_file_object(caller_namespace=_, more_keyword_parameters)
Ah-hah! This works from within my DTMLFile method, like so: <dtml-let key1="val1" key2="val2"> <dtml-var expr="my_dtmlfile_method(_.None, _, caller_namespace=_, key1=val1)"> </dtml-let> Then it is possible to use *both* key1 and key2 in "my_dtmlfile_method". (In practice, key1 probably needed to be passed something other than the value it already had to justify this call). (Zope 2.5.1). Thanks Dieter! This is definitely going in my product's developer documentation. ;-D Cheers, Terry -- ------------------------------------------------------ Terry Hancock hancock@anansispaceworks.com Anansi Spaceworks http://www.anansispaceworks.com P.O. Box 60583 Pasadena, CA 91116-6583 ------------------------------------------------------
participants (2)
-
Dieter Maurer -
Terry Hancock