Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Z
Zope
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
Zope
Commits
c75f735c
Commit
c75f735c
authored
Jun 09, 2000
by
Evan Simpson
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Merge Before-traverse-Dev-branch
parent
dbf73423
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
330 additions
and
196 deletions
+330
-196
lib/python/OFS/SimpleItem.py
lib/python/OFS/SimpleItem.py
+30
-19
lib/python/ZPublisher/BaseRequest.py
lib/python/ZPublisher/BaseRequest.py
+116
-134
lib/python/ZPublisher/BeforeTraverse.py
lib/python/ZPublisher/BeforeTraverse.py
+113
-0
lib/python/ZPublisher/HTTPRequest.py
lib/python/ZPublisher/HTTPRequest.py
+71
-43
No files found.
lib/python/OFS/SimpleItem.py
View file @
c75f735c
...
...
@@ -89,8 +89,8 @@ Aqueduct database adapters, etc.
This module can also be used as a simple template for implementing new
item types.
$Id: SimpleItem.py,v 1.7
5 2000/06/01 17:29:37 jim
Exp $'''
__version__
=
'$Revision: 1.7
5
$'
[
11
:
-
2
]
$Id: SimpleItem.py,v 1.7
6 2000/06/09 23:57:46 evan
Exp $'''
__version__
=
'$Revision: 1.7
6
$'
[
11
:
-
2
]
import
regex
,
sys
,
Globals
,
App
.
Management
,
Acquisition
,
App
.
Undo
import
AccessControl.Role
,
AccessControl
.
Owned
,
App
.
Common
...
...
@@ -338,14 +338,19 @@ class Item(Base, Resource, CopySource, App.Management.Tabs,
return
1
def
absolute_url
(
self
,
relative
=
0
):
id
=
quote
(
self
.
id
)
p
=
getattr
(
self
,
'aq_inner'
,
None
)
if
p
is
not
None
:
url
=
p
.
aq_parent
.
absolute_url
(
relative
)
if
url
:
id
=
url
+
'/'
+
id
return
id
req
=
self
.
REQUEST
rpp
=
req
.
get
(
'VirtualRootPhysicalPath'
,
(
''
,))
spp
=
self
.
getPhysicalPath
()
i
=
0
for
name
in
rpp
[:
len
(
spp
)]:
if
spp
[
i
]
==
name
:
i
=
i
+
1
else
:
break
path
=
map
(
quote
,
spp
[
i
:])
if
relative
:
# Deprecated - use getPhysicalPath
return
join
(
path
,
'/'
)
return
join
([
req
[
'SERVER_URL'
]]
+
req
.
_script
+
path
,
'/'
)
def
getPhysicalPath
(
self
):
'''Returns a path (an immutable sequence of strings)
...
...
@@ -462,14 +467,20 @@ class Item_w__name__(Item):
self
.
__name__
=
id
def
absolute_url
(
self
,
relative
=
0
):
id
=
quote
(
self
.
__name__
)
p
=
getattr
(
self
,
'aq_inner'
,
None
)
if
p
is
not
None
:
url
=
p
.
aq_parent
.
absolute_url
(
relative
)
if
url
:
id
=
url
+
'/'
+
id
return
id
req
=
self
.
REQUEST
rpp
=
req
.
get
(
'VirtualRootPhysicalPath'
,
(
''
,))
spp
=
self
.
getPhysicalPath
()
i
=
0
for
name
in
rpp
[:
len
(
spp
)]:
if
spp
[
i
]
==
name
:
i
=
i
+
1
else
:
break
path
=
map
(
quote
,
spp
[
i
:])
if
relative
:
# This is useful for physical path relative to a VirtualRoot
return
join
(
path
,
'/'
)
return
join
([
req
[
'SERVER_URL'
]]
+
req
.
_script
+
path
,
'/'
)
def
getPhysicalPath
(
self
):
'''Returns a path (an immutable sequence of strings)
...
...
lib/python/ZPublisher/BaseRequest.py
View file @
c75f735c
...
...
@@ -82,7 +82,7 @@
# attributions are listed in the accompanying credits file.
#
##############################################################################
__version__
=
'$Revision: 1.2
6
$'
[
11
:
-
2
]
__version__
=
'$Revision: 1.2
7
$'
[
11
:
-
2
]
from
string
import
join
,
split
,
find
,
rfind
,
lower
,
upper
from
urllib
import
quote
...
...
@@ -258,16 +258,11 @@ class BaseRequest:
method
=
'index_html'
else
:
baseflag
=
1
URL
=
request
[
'URL'
]
parents
=
request
[
'PARENTS'
]
object
=
parents
[
-
1
]
del
parents
[:]
try
:
# We build parents in the wrong order, so we
# need to make sure we reverse it when we're doe.
roles
=
getattr
(
object
,
'__roles__'
,
UNSPECIFIED_ROLES
)
roles
=
getattr
(
object
,
'__roles__'
,
UNSPECIFIED_ROLES
)
# if the top object has a __bobo_traverse__ method, then use it
# to possibly traverse to an alternate top-level object.
...
...
@@ -275,54 +270,63 @@ class BaseRequest:
try
:
object
=
object
.
__bobo_traverse__
(
request
)
except
:
pass
# Get default object if no path was specified:
if
not
path
:
if
not
method
:
return
response
.
forbiddenError
(
entry_name
)
entry_name
=
method
try
:
if
hasattr
(
object
,
entry_name
):
response
.
setBase
(
URL
)
path
=
[
entry_name
]
else
:
try
:
if
object
.
has_key
(
entry_name
):
path
=
[
entry_name
]
except
:
pass
except
:
pass
if
not
path
and
not
method
:
return
response
.
forbiddenError
(
self
[
'URL'
])
# Traverse the URL to find the object:
if
hasattr
(
object
,
'__of__'
):
# Try to bind the top-level object to the request
# This is how you get 'self.REQUEST'
object
=
object
.
__of__
(
RequestContainer
(
REQUEST
=
request
))
parents
.
append
(
object
)
steps
=
self
.
steps
self
.
_steps
=
_steps
=
map
(
quote
,
steps
)
path
.
reverse
()
pop
=
path
.
pop
# request['path']=path
pop
=
path
.
pop
request
[
'TraversalRequestNameStack'
]
=
request
.
path
=
path
while
path
:
entry_name
=
pop
()
URL
=
"%s/%s"
%
(
URL
,
quote
(
entry_name
))
got
=
0
# Can't find it? XXX
if
entry_name
:
entry_name
=
''
try
:
# We build parents in the wrong order, so we
# need to make sure we reverse it when we're doe.
while
1
:
bpth
=
getattr
(
object
,
'__before_publishing_traverse__'
,
None
)
if
bpth
is
not
None
:
bpth
(
object
,
self
)
# Check for method:
if
path
:
entry_name
=
pop
()
elif
(
method
and
hasattr
(
object
,
method
)
and
entry_name
!=
method
and
getattr
(
object
,
method
)
is
not
None
):
request
.
_hacked_path
=
1
entry_name
=
method
else
:
if
(
hasattr
(
object
,
'__call__'
)
and
hasattr
(
object
.
__call__
,
'__roles__'
)):
roles
=
object
.
__call__
.
__roles__
if
request
.
_hacked_path
:
i
=
rfind
(
URL
,
'/'
)
if
i
>
0
:
response
.
setBase
(
URL
[:
i
])
break
if
not
entry_name
:
continue
step
=
quote
(
entry_name
)
_steps
.
append
(
step
)
request
[
'URL'
]
=
URL
=
'%s/%s'
%
(
request
[
'URL'
],
step
)
got
=
0
if
entry_name
[:
1
]
==
'_'
:
if
debug_mode
:
return
response
.
debugError
(
"Object name begins with an underscore at: %s"
%
URL
)
"Object name begins with an underscore at: %s"
%
URL
)
else
:
return
response
.
forbiddenError
(
entry_name
)
if
hasattr
(
object
,
'__bobo_traverse__'
):
request
[
'URL'
]
=
URL
subobject
=
object
.
__bobo_traverse__
(
request
,
entry_name
)
if
type
(
subobject
)
is
type
(())
and
len
(
subobject
)
>
1
:
# Add additional parents into the path
while
len
(
subobject
)
>
2
:
parents
.
append
(
subobject
[
0
])
subobject
=
subobject
[
1
:]
object
,
subobject
=
subobject
parents
[
-
1
:]
=
subobject
[:
-
1
]
object
,
subobject
=
subobject
[
-
2
:]
else
:
try
:
...
...
@@ -348,8 +352,9 @@ class BaseRequest:
TypeError
,
AttributeError
):
if
debug_mode
:
return
response
.
debugError
(
"Cannot locate object at: %s"
%
URL
)
else
:
return
response
.
notFoundError
(
URL
)
"Cannot locate object at: %s"
%
URL
)
else
:
return
response
.
notFoundError
(
URL
)
try
:
try
:
doc
=
subobject
.
__doc__
...
...
@@ -359,44 +364,22 @@ class BaseRequest:
if
debug_mode
:
return
response
.
debugError
(
"Missing doc string at: %s"
%
URL
)
else
:
return
response
.
notFoundError
(
"%s"
%
(
URL
)
)
else
:
return
response
.
notFoundError
(
"%s"
%
URL
)
r
=
getattr
(
subobject
,
'__roles__'
,
UNSPECIFIED_ROLES
)
r
=
getattr
(
subobject
,
'__roles__'
,
UNSPECIFIED_ROLES
)
if
r
is
not
UNSPECIFIED_ROLES
:
roles
=
r
roles
=
r
elif
not
got
:
roles
=
getattr
(
subobject
,
entry_name
+
'__roles__'
,
roles
)
roles
=
getattr
(
subobject
,
entry_name
+
'__roles__'
,
roles
)
# Promote subobject to object
parents
.
append
(
object
)
object
=
subobject
steps
.
append
(
entry_name
)
# Check for method:
if
not
path
:
if
(
method
and
hasattr
(
object
,
method
)
and
entry_name
!=
method
and
getattr
(
object
,
method
)
is
not
None
):
request
.
_hacked_path
=
1
path
.
append
(
method
)
else
:
if
(
hasattr
(
object
,
'__call__'
)
and
hasattr
(
object
.
__call__
,
'__roles__'
)):
roles
=
object
.
__call__
.
__roles__
if
request
.
_hacked_path
:
i
=
rfind
(
URL
,
'/'
)
if
i
>
0
:
response
.
setBase
(
URL
[:
i
])
except
:
# Save the last found object before handling the exception.
if
object
is
not
None
:
parents
.
append
(
object
)
parents
.
reverse
()
raise
steps
.
append
(
entry_name
)
finally
:
parents
.
reverse
()
parents
.
pop
(
0
)
# Get rid of final method object
# Do authorization checks
user
=
groups
=
None
...
...
@@ -459,11 +442,10 @@ class BaseRequest:
if
user
is
None
and
roles
!=
UNSPECIFIED_ROLES
:
response
.
unauthorized
()
steps
=
join
(
steps
[:
-
i
],
'/'
)
if
user
is
not
None
:
if
validated_hook
is
not
None
:
validated_hook
(
self
,
user
)
request
[
'AUTHENTICATED_USER'
]
=
user
request
[
'AUTHENTICATION_PATH'
]
=
steps
request
[
'AUTHENTICATION_PATH'
]
=
join
(
steps
[:
-
i
],
'/'
)
# Remove http request method from the URL.
request
[
'URL'
]
=
URL
...
...
lib/python/ZPublisher/BeforeTraverse.py
0 → 100644
View file @
c75f735c
"""BeforeTraverse interface and helper classes"""
# Interface
def
registerBeforeTraverse
(
container
,
object
,
app_handle
,
priority
=
99
):
"""Register an object to be called before a container is traversed.
'app_handle' should be a string or other hashable value that
distinguishes the application of this object, and which must
be used in order to unregister the object.
If the container will be pickled, the object must be a callable class
instance, not a function or method.
'priority' is optional, and determines the relative order in which
registered objects will be called.
"""
btr
=
getattr
(
container
,
'__before_traverse__'
,
{})
btr
[(
priority
,
app_handle
)]
=
object
rewriteBeforeTraverse
(
container
,
btr
)
def
unregisterBeforeTraverse
(
container
,
app_handle
):
"""Unregister a __before_traverse__ hook object, given its 'app_handle'.
Returns a list of unregistered objects."""
btr
=
getattr
(
container
,
'__before_traverse__'
,
{})
objects
=
[]
for
k
in
btr
.
keys
():
if
k
[
1
]
==
app_handle
:
objects
.
append
(
btr
[
k
])
del
btr
[
k
]
if
objects
:
rewriteBeforeTraverse
(
container
,
btr
)
return
objects
def
queryBeforeTraverse
(
container
,
app_handle
):
"""Find __before_traverse__ hook objects, given an 'app_handle'.
Returns a list of (priority, object) pairs."""
btr
=
getattr
(
container
,
'__before_traverse__'
,
{})
objects
=
[]
for
k
in
btr
.
keys
():
if
k
[
1
]
==
app_handle
:
objects
.
append
((
k
[
0
],
btr
[
k
]))
return
objects
# Implementation tools
def
rewriteBeforeTraverse
(
container
,
btr
):
"""Rewrite the list of __before_traverse__ hook objects"""
container
.
__before_traverse__
=
btr
hookname
=
'__before_publishing_traverse__'
dic
=
hasattr
(
container
.
__class__
,
hookname
)
bpth
=
container
.
__dict__
.
get
(
hookname
,
None
)
if
isinstance
(
bpth
,
MultiHook
):
bpth
=
bpth
.
_prior
bpth
=
MultiHook
(
hookname
,
bpth
,
dic
)
setattr
(
container
,
hookname
,
bpth
)
keys
=
btr
.
keys
()
keys
.
sort
()
for
key
in
keys
:
bpth
.
add
(
btr
[
key
])
class
MultiHook
:
"""Class used to multiplex hook.
MultiHook calls the named hook from the class of the container, then
the prior hook, then all the hooks in its list.
"""
def
__init__
(
self
,
hookname
,
prior
,
defined_in_class
):
self
.
_hookname
=
hookname
self
.
_prior
=
prior
self
.
_defined_in_class
=
defined_in_class
self
.
_list
=
[]
def
__call__
(
self
,
container
,
request
):
if
self
.
_defined_in_class
:
# Assume it's an unbound method
getattr
(
container
.
__class__
,
self
.
_hookname
)(
container
,
request
)
prior
=
self
.
_prior
if
prior
is
not
None
:
prior
(
container
,
request
)
for
cob
in
self
.
_list
:
cob
(
container
,
request
)
def
add
(
self
,
cob
):
self
.
_list
.
append
(
cob
)
# Helper class
class
NameCaller
:
"""Class used to proxy sibling objects by name.
When called with a container and request object, it gets the named
attribute from the container and calls it. If the name is not
found, it fails silently.
>>> registerBeforeTraverse(folder, NameCaller('preop'), 'XApp')
"""
def
__init__
(
self
,
name
):
self
.
name
=
name
def
__call__
(
self
,
container
,
request
):
try
:
meth
=
getattr
(
container
,
self
.
name
)
except
AttributeError
:
return
args
=
getattr
(
getattr
(
meth
,
'func_code'
,
None
),
'co_argcount'
,
2
)
apply
(
meth
,
(
container
,
request
,
None
)[:
args
])
lib/python/ZPublisher/HTTPRequest.py
View file @
c75f735c
...
...
@@ -83,14 +83,14 @@
#
##############################################################################
__version__
=
'$Revision: 1.3
4
$'
[
11
:
-
2
]
__version__
=
'$Revision: 1.3
5
$'
[
11
:
-
2
]
import
regex
,
sys
,
os
,
string
import
regex
,
sys
,
os
,
string
,
urllib
from
string
import
lower
,
atoi
,
rfind
,
split
,
strip
,
join
,
upper
,
find
from
BaseRequest
import
BaseRequest
from
HTTPResponse
import
HTTPResponse
from
cgi
import
FieldStorage
from
urllib
import
quote
,
unquote
from
urllib
import
quote
,
unquote
,
splittype
,
splitport
from
Converters
import
get_converter
from
maybe_lock
import
allocate_lock
xmlrpc
=
None
# Placeholder for module that we'll import if we have to.
...
...
@@ -120,6 +120,8 @@ hide_key={'HTTP_AUTHORIZATION':1,
'HTTP_CGI_AUTHORIZATION'
:
1
,
}.
has_key
default_port
=
{
'http'
:
'80'
,
'https'
:
'443'
}
_marker
=
[]
class
HTTPRequest
(
BaseRequest
):
"""
\
...
...
@@ -186,19 +188,46 @@ class HTTPRequest(BaseRequest):
return
r
def
setSite
(
self
,
base
=
None
):
""" blah """
if
base
is
not
None
:
self
.
script
=
self
.
base
=
base
# reset the URL
self
.
other
[
'URL'
]
=
self
.
script
# clear the 'cache' of URLx and BASEx
def
setServerURL
(
self
,
protocol
=
None
,
hostname
=
None
,
port
=
None
):
""" Set the parts of generated URLs. """
other
=
self
.
other
server_url
=
other
.
get
(
'SERVER_URL'
,
''
)
if
protocol
is
None
and
hostname
is
None
and
port
is
None
:
return
server_url
oldprotocol
,
oldhost
=
splittype
(
server_url
)
oldhostname
,
oldport
=
splitport
(
oldhost
[
2
:])
if
protocol
is
None
:
protocol
=
oldprotocol
if
hostname
is
None
:
hostname
=
oldhostname
if
port
is
None
:
port
=
oldport
if
(
port
is
None
or
default_port
[
protocol
]
==
port
):
host
=
hostname
else
:
host
=
hostname
+
':'
+
port
server_url
=
other
[
'SERVER_URL'
]
=
'%s://%s'
%
(
protocol
,
host
)
self
.
_resetURLS
()
return
server_url
def
setVirtualRoot
(
self
,
path
,
hard
=
0
):
""" Treat the current publishing object as a VirtualRoot """
other
=
self
.
other
if
type
(
path
)
is
type
(
''
):
path
=
filter
(
None
,
split
(
path
,
'/'
))
self
.
_script
[:]
=
map
(
quote
,
path
)
del
self
.
_steps
[:]
parents
=
other
[
'PARENTS'
]
if
hard
:
del
parents
[:
-
1
]
other
[
'VirtualRootPhysicalPath'
]
=
parents
[
-
1
].
getPhysicalPath
()
self
.
_resetURLS
()
def
_resetURLS
(
self
):
other
=
self
.
other
other
[
'URL'
]
=
join
([
other
[
'SERVER_URL'
]]
+
self
.
_script
+
self
.
_steps
,
'/'
)
for
x
in
self
.
_urls
:
del
self
.
other
[
x
]
del
self
.
other
[
'PARENTS'
][:]
self
.
_urls
=
()
def
__init__
(
self
,
stdin
,
environ
,
response
,
clean
=
0
):
self
.
_orig_env
=
environ
...
...
@@ -221,11 +250,16 @@ class HTTPRequest(BaseRequest):
other
=
self
.
other
=
{
'RESPONSE'
:
response
}
self
.
form
=
{}
self
.
steps
=
[]
self
.
_steps
=
[]
################################################################
# Get base info first. This isn't likely to cause
# errors and might be useful to error handlers.
b
=
script
=
strip
(
get_env
(
'SCRIPT_NAME'
,
''
))
# _script and the other _names are meant for URL construction
self
.
_script
=
map
(
quote
,
filter
(
None
,
split
(
script
,
'/'
)))
while
b
and
b
[
-
1
]
==
'/'
:
b
=
b
[:
-
1
]
p
=
rfind
(
b
,
'/'
)
if
p
>=
0
:
b
=
b
[:
p
+
1
]
...
...
@@ -234,23 +268,24 @@ class HTTPRequest(BaseRequest):
server_url
=
get_env
(
'SERVER_URL'
,
None
)
if
server_url
is
not
None
:
server_url
=
strip
(
server_url
)
other
[
'SERVER_URL'
]
=
server_url
=
strip
(
server_url
)
else
:
if
have_env
(
'HTTPS'
)
and
(
environ
[
'HTTPS'
]
==
"on"
or
environ
[
'HTTPS'
]
==
"ON"
):
server_url
=
'https://
'
protocol
=
'https
'
elif
(
have_env
(
'SERVER_PORT_SECURE'
)
and
environ
[
'SERVER_PORT_SECURE'
]
==
"1"
):
server_url
=
'https://
'
else
:
server_url
=
'http://
'
protocol
=
'https
'
else
:
protocol
=
'http
'
if
have_env
(
'HTTP_HOST'
):
server_url
=
server_url
+
strip
(
environ
[
'HTTP_HOST'
])
host
=
strip
(
environ
[
'HTTP_HOST'
])
hostname
,
port
=
splitport
(
host
)
else
:
server_url
=
server_url
+
strip
(
environ
[
'SERVER_NAME'
])
server_port
=
environ
[
'SERVER_PORT'
]
if
server_port
!=
'80'
:
server_url
=
server_url
+
':'
+
server_port
other
[
'SERVER_URL'
]
=
server_url
hostname
=
strip
(
environ
[
'SERVER_NAME'
])
port
=
environ
[
'SERVER_PORT'
]
self
.
setServerURL
(
protocol
=
protocol
,
hostname
=
hostname
,
port
=
port
)
server_url
=
other
[
'SERVER_URL'
]
if
server_url
[
-
1
:]
==
'/'
:
server_url
=
server_url
[:
-
1
]
...
...
@@ -437,7 +472,7 @@ class HTTPRequest(BaseRequest):
reclist
.
reverse
()
if
not
hasattr
(
x
,
attr
):
#If the attribute does not
#exist, set
it
#exist, setit
if
flags
&
SEQUENCE
:
item
=
[
item
]
reclist
.
remove
(
x
)
setattr
(
x
,
attr
,
item
)
...
...
@@ -722,13 +757,11 @@ class HTTPRequest(BaseRequest):
return
other
[
key
]
if
key
[:
1
]
==
'U'
and
URLmatch
(
key
)
>=
0
:
n
=
atoi
(
key
[
3
:])
URL
=
other
[
'URL'
]
for
i
in
range
(
0
,
n
):
l
=
rfind
(
URL
,
'/'
)
if
l
>=
0
:
URL
=
URL
[:
l
]
else
:
raise
KeyError
,
key
if
len
(
URL
)
<
len
(
self
.
base
)
and
n
>
1
:
raise
KeyError
,
key
path
=
self
.
_script
+
self
.
_steps
n
=
len
(
path
)
-
atoi
(
key
[
3
:])
if
n
<
0
:
raise
KeyError
,
key
URL
=
join
([
other
[
'SERVER_URL'
]]
+
path
[:
n
],
'/'
)
other
[
key
]
=
URL
self
.
_urls
=
self
.
_urls
+
(
key
,)
return
URL
...
...
@@ -743,20 +776,17 @@ class HTTPRequest(BaseRequest):
if
key
[:
1
]
==
'B'
:
if
BASEmatch
(
key
)
>=
0
:
n
=
atoi
(
key
[
4
:])
path
=
self
.
_steps
n
=
atoi
(
key
[
4
:])
if
n
:
n
=
n
-
1
if
len
(
self
.
steps
)
<
n
:
n
=
n
-
1
if
len
(
path
)
<
n
:
raise
KeyError
,
key
v
=
self
.
script
while
v
[
-
1
:]
==
'/'
:
v
=
v
[:
-
1
]
v
=
join
([
v
]
+
self
.
steps
[:
n
],
'/'
)
v
=
self
.
_script
+
path
[:
n
]
else
:
v
=
self
.
base
while
v
[
-
1
:]
==
'/'
:
v
=
v
[:
-
1
]
other
[
key
]
=
v
v
=
self
.
_script
[:
-
1
]
other
[
key
]
=
v
=
join
([
other
[
'SERVER_URL'
]]
+
v
,
'/'
)
self
.
_urls
=
self
.
_urls
+
(
key
,)
return
v
...
...
@@ -977,8 +1007,6 @@ def parse_cookie(text,
return apply(parse_cookie,(text[l:],result))
import sys
# add class
class record:
def __getattr__(self, key, default=None):
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment