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
d8eaafb3
Commit
d8eaafb3
authored
May 17, 2001
by
Shane Hathaway
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
The Refresh product.
parent
1e9da134
Changes
7
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
888 additions
and
40 deletions
+888
-40
doc/CHANGES.txt
doc/CHANGES.txt
+4
-0
lib/python/App/Product.py
lib/python/App/Product.py
+135
-0
lib/python/App/RefreshFuncs.py
lib/python/App/RefreshFuncs.py
+456
-0
lib/python/App/dtml/refresh.dtml
lib/python/App/dtml/refresh.dtml
+118
-0
lib/python/OFS/Application.py
lib/python/OFS/Application.py
+107
-39
lib/python/Products/OFSP/help/Product_Refresh.stx
lib/python/Products/OFSP/help/Product_Refresh.stx
+62
-0
lib/python/Zope/__init__.py
lib/python/Zope/__init__.py
+6
-1
No files found.
doc/CHANGES.txt
View file @
d8eaafb3
...
...
@@ -57,6 +57,10 @@ Zope changes
- DTML-In and DTML-Tree now have optional "prefix" attributes
that can be used to make friendlier tag variable names.
- Added product reloading capability, formerly provided by
the "Refresh" product. This enables developers to see the
effect of changes to their products without restarting Zope.
Zope 2.3.0 beta 3
Features Added
...
...
lib/python/App/Product.py
View file @
d8eaafb3
...
...
@@ -116,6 +116,7 @@ from Factory import Factory
from
Permission
import
PermissionManager
import
ZClasses
,
ZClasses
.
ZClass
from
HelpSys.HelpSys
import
ProductHelp
import
RefreshFuncs
class
ProductFolder
(
Folder
):
...
...
@@ -341,6 +342,129 @@ class Product(Folder, PermissionManager):
self
.
_setObject
(
'Help'
,
ProductHelp
(
'Help'
,
self
.
id
))
return
self
.
Help
#
# Product refresh
#
_refresh_dtml
=
Globals
.
DTMLFile
(
'dtml/refresh'
,
globals
())
def
_readRefreshTxt
(
self
,
pid
=
None
):
refresh_txt
=
None
if
pid
is
None
:
pid
=
self
.
id
for
productDir
in
Products
.
__path__
:
found
=
0
for
name
in
(
'refresh.txt'
,
'REFRESH.txt'
,
'REFRESH.TXT'
):
p
=
os
.
path
.
join
(
productDir
,
pid
,
name
)
if
os
.
path
.
exists
(
p
):
found
=
1
break
if
found
:
try
:
file
=
open
(
p
)
text
=
file
.
read
()
file
.
close
()
refresh_txt
=
text
break
except
:
# Not found here.
pass
return
refresh_txt
def
manage_refresh
(
self
,
REQUEST
,
manage_tabs_message
=
None
):
'''
Displays the refresh management screen.
'''
error_type
=
error_value
=
error_tb
=
None
exc
=
RefreshFuncs
.
getLastRefreshException
(
self
.
id
)
if
exc
is
not
None
:
error_type
,
error_value
,
error_tb
=
exc
exc
=
None
refresh_txt
=
self
.
_readRefreshTxt
()
# Read the persistent refresh information.
auto
=
RefreshFuncs
.
isAutoRefreshEnabled
(
self
.
_p_jar
,
self
.
id
)
deps
=
RefreshFuncs
.
getDependentProducts
(
self
.
_p_jar
,
self
.
id
)
# List all product modules.
mods
=
RefreshFuncs
.
listRefreshableModules
(
self
.
id
)
loaded_modules
=
[]
prefix
=
'Products.%s'
%
self
.
id
prefixdot
=
prefix
+
'.'
lpdot
=
len
(
prefixdot
)
for
name
,
module
in
mods
:
if
name
==
prefix
or
name
[:
lpdot
]
==
prefixdot
:
name
=
name
[
lpdot
:]
if
not
name
:
name
=
'__init__'
loaded_modules
.
append
(
name
)
all_auto
=
RefreshFuncs
.
listAutoRefreshableProducts
(
self
.
_p_jar
)
for
pid
in
all_auto
:
# Ignore products that don't have a refresh.txt.
if
self
.
_readRefreshTxt
(
pid
)
is
None
:
all_auto
.
remove
(
pid
)
auto_other
=
filter
(
lambda
productId
,
myId
=
self
.
id
:
productId
!=
myId
,
all_auto
)
# Return rendered DTML.
return
self
.
_refresh_dtml
(
REQUEST
,
id
=
self
.
id
,
refresh_txt
=
refresh_txt
,
error_type
=
error_type
,
error_value
=
error_value
,
error_tb
=
error_tb
,
devel_mode
=
Globals
.
DevelopmentMode
,
auto_refresh_enabled
=
auto
,
auto_refresh_other
=
auto_other
,
dependent_products
=
deps
,
loaded_modules
=
loaded_modules
,
manage_tabs_message
=
manage_tabs_message
,
management_view
=
'Refresh'
)
def
manage_performRefresh
(
self
,
REQUEST
=
None
):
'''
Attempts to perform a refresh operation.
'''
if
self
.
_readRefreshTxt
()
is
None
:
raise
'Unauthorized'
,
'refresh.txt not found'
message
=
None
if
RefreshFuncs
.
performFullRefresh
(
self
.
_p_jar
,
self
.
id
):
from
ZODB
import
Connection
Connection
.
updateCodeTimestamp
()
# Clears cache in next connection.
message
=
'Product refreshed.'
else
:
message
=
'An exception occurred.'
if
REQUEST
is
not
None
:
return
self
.
manage_refresh
(
REQUEST
,
manage_tabs_message
=
message
)
def
manage_enableAutoRefresh
(
self
,
enable
=
0
,
REQUEST
=
None
):
'''
Changes the auto refresh flag for this product.
'''
if
self
.
_readRefreshTxt
()
is
None
:
raise
'Unauthorized'
,
'refresh.txt not created'
RefreshFuncs
.
enableAutoRefresh
(
self
.
_p_jar
,
self
.
id
,
enable
)
if
enable
:
message
=
'Enabled auto refresh.'
else
:
message
=
'Disabled auto refresh.'
if
REQUEST
is
not
None
:
return
self
.
manage_refresh
(
REQUEST
,
manage_tabs_message
=
message
)
def
manage_selectDependentProducts
(
self
,
selections
=
(),
REQUEST
=
None
):
'''
Selects which products to refresh simultaneously.
'''
if
self
.
_readRefreshTxt
()
is
None
:
raise
'Unauthorized'
,
'refresh.txt not created'
RefreshFuncs
.
setDependentProducts
(
self
.
_p_jar
,
self
.
id
,
selections
)
if
REQUEST
is
not
None
:
return
self
.
manage_refresh
(
REQUEST
)
class
CompressedOutputFile
:
def
__init__
(
self
,
rot
):
self
.
_c
=
zlib
.
compressobj
()
...
...
@@ -477,6 +601,17 @@ def initializeProduct(productp, name, home, app):
)
break
# Ensure this product has a refresh tab.
found
=
0
for
option
in
product
.
manage_options
:
if
option
.
get
(
'label'
)
==
'Refresh'
:
found
=
1
break
if
not
found
:
product
.
manage_options
=
product
.
manage_options
+
(
{
'label'
:
'Refresh'
,
'action'
:
'manage_refresh'
,
'help'
:
(
'OFSP'
,
'Product_Refresh.stx'
)},)
if
(
os
.
environ
.
get
(
'ZEO_CLIENT'
)
and
not
os
.
environ
.
get
(
'FORCE_PRODUCT_LOAD'
)):
get_transaction
().
abort
()
...
...
lib/python/App/RefreshFuncs.py
0 → 100644
View file @
d8eaafb3
This diff is collapsed.
Click to expand it.
lib/python/App/dtml/refresh.dtml
0 → 100644
View file @
d8eaafb3
<dtml-comment>
Arguments for this method:
id, refresh_txt, error_type, error_value, error_tb, devel_mode,
auto_refresh_enabled, auto_refresh_other, dependent_products,
loaded_modules
</dtml-comment>
<dtml-let
form_title=
"'Refresh product: ' + id"
>
<dtml-if
manage_page_header
>
<dtml-var
manage_page_header
>
<dtml-else>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html
lang=
"en"
>
<head>
<title>
&dtml-form_title;
</title>
</head>
<body
bgcolor=
"#FFFFFF"
link=
"#000099"
vlink=
"#555555"
>
<h3>
&dtml-form_title;
</h3>
</dtml-if>
</dtml-let>
<dtml-var
manage_tabs
>
<dtml-if
expr=
"refresh_txt == _.None"
>
<p>
The refresh function, designed to ease the development of Zope
products, is not currently enabled for this product.
To make it available, put a file named "refresh.txt" in the
&dtml-id;
product directory. Please note that not all products are
compatible with the refresh function.
</p>
<dtml-else>
<dtml-if
error_type
>
<p><b>
An exception occurred during the last refresh.
</b><br
/>
Exception type:
<b>
&dtml-error_type;
</b>
<br
/>
Exception value:
<b>
&dtml-error_value;
</b>
</p>
<pre>
&dtml-error_tb;
</pre>
<hr
/>
</dtml-if>
<form
action=
"&dtml-absolute_url;"
method=
"POST"
>
<table
border=
"0"
>
<tr>
<td
valign=
"top"
>
<dtml-if
expr=
"_.string.strip(refresh_txt)"
>
<p>
<b>
Important information about refreshing this product:
</b><br
/>
<dtml-var
refresh_txt
fmt=
"structured-text"
>
</p>
</dtml-if>
<div
align=
"center"
><input
type=
"submit"
name=
"manage_performRefresh:method"
value=
"Refresh this product"
/>
</div>
<p>
<dtml-if
auto_refresh_enabled
>
<dtml-if
devel_mode
>
Auto refresh is enabled. Zope will repeatedly scan for
changes to the Python modules that make up this product and
execute a refresh when needed.
<dtml-else>
Although auto refresh is enabled, Zope is not in development
mode so auto refresh is not available. Use the "-D" argument
when starting Zope to enable development mode.
</dtml-if>
<dtml-else>
Auto refresh is disabled. Enable auto refresh
to cause Zope to frequently scan this product for changes.
Note that auto refresh can slow down Zope considerably
if enabled for more than a few products.
</dtml-if>
<br
/>
<dtml-let
checked=
"auto_refresh_enabled and 'checked' or ' '"
>
<input
type=
"checkbox"
name=
"enable"
value=
"1"
&
dtml-checked
;
/>
Auto refresh mode
<input
type=
"submit"
name=
"manage_enableAutoRefresh:method"
value=
"Change"
/>
</dtml-let>
</p>
<dtml-if
auto_refresh_other
>
<p>
Select dependent auto-refreshable products to be refreshed
simultaneously.
<br
/>
<dtml-in
auto_refresh_other
sort
>
<dtml-let
checked=
"(_['sequence-item'] in dependent_products) and
'checked' or ' '"
>
<input
type=
"checkbox"
name=
"selections:list"
value=
"&dtml-sequence-item;"
&
dtml-checked
;
/>
<a
href=
"../&dtml-sequence-item;/manage_refresh"
>
&dtml-sequence-item;
</a><br
/>
</dtml-let>
</dtml-in>
<input
type=
"submit"
name=
"manage_selectDependentProducts:method"
value=
"Change"
/>
</p>
</dtml-if>
</td>
<td
valign=
"top"
class=
"row-hilite"
>
<p><b>
Refreshable product modules:
</b></p>
<ul>
<dtml-in
loaded_modules
sort
>
<li>
&dtml-sequence-item;
</li>
</dtml-in>
</ul>
</td>
</tr>
</table>
</form>
</dtml-if>
<dtml-if
manage_page_footer
>
<dtml-var
manage_page_footer
>
<dtml-else>
</body></html>
</dtml-if>
lib/python/OFS/Application.py
View file @
d8eaafb3
...
...
@@ -85,8 +85,8 @@
__doc__
=
'''Application support
$Id: Application.py,v 1.14
3 2001/04/07 16:37:07 jim
Exp $'''
__version__
=
'$Revision: 1.14
3
$'
[
11
:
-
2
]
$Id: Application.py,v 1.14
4 2001/05/17 18:35:09 shane
Exp $'''
__version__
=
'$Revision: 1.14
4
$'
[
11
:
-
2
]
import
Globals
,
Folder
,
os
,
sys
,
App
.
Product
,
App
.
ProductRegistry
,
misc_
import
time
,
traceback
,
os
,
string
,
Products
...
...
@@ -495,16 +495,8 @@ def initialize(app):
get_transaction
().
abort
()
def
import_products
(
_st
=
type
(
''
)
):
def
import_products
():
# Try to import each product, checking for and catching errors.
path_join
=
os
.
path
.
join
isdir
=
os
.
path
.
isdir
exists
=
os
.
path
.
exists
DictType
=
type
({})
global_dict
=
globals
()
silly
=
(
'__doc__'
,)
modules
=
sys
.
modules
have_module
=
modules
.
has_key
done
=
{}
for
product_dir
in
Products
.
__path__
:
...
...
@@ -516,12 +508,26 @@ def import_products(_st=type('')):
if
done
.
has_key
(
product_name
):
continue
done
[
product_name
]
=
1
import_product
(
product_dir
,
product_name
)
def
import_product
(
product_dir
,
product_name
,
raise_exc
=
0
,
log_exc
=
1
):
path_join
=
os
.
path
.
join
isdir
=
os
.
path
.
isdir
exists
=
os
.
path
.
exists
_st
=
type
(
''
)
global_dict
=
globals
()
silly
=
(
'__doc__'
,)
modules
=
sys
.
modules
have_module
=
modules
.
has_key
if
1
:
# Preserve indentation for diff :-)
try
:
package_dir
=
path_join
(
product_dir
,
product_name
)
if
not
isdir
(
package_dir
):
continue
if
not
isdir
(
package_dir
):
return
if
not
exists
(
path_join
(
package_dir
,
'__init__.py'
)):
if
not
exists
(
path_join
(
package_dir
,
'__init__.pyc'
)):
continue
return
pname
=
"Products.%s"
%
product_name
try
:
...
...
@@ -532,35 +538,27 @@ def import_products(_st=type('')):
if
type
(
v
)
is
_st
and
have_module
(
v
):
v
=
modules
[
v
]
modules
[
k
]
=
v
except
:
LOG
(
'Zope'
,
ERROR
,
'Couldn
\
'
t import %s'
%
pname
,
error
=
sys
.
exc_info
())
exc
=
sys
.
exc_info
()
if
log_exc
:
LOG
(
'Zope'
,
ERROR
,
'Could not import %s'
%
pname
,
error
=
exc
)
f
=
StringIO
()
traceback
.
print_exc
(
100
,
f
)
f
=
f
.
getvalue
()
try
:
modules
[
pname
].
__import_error__
=
f
except
:
pass
if
raise_exc
:
raise
exc
[
0
],
exc
[
1
],
exc
[
2
]
finally
:
exc
=
None
def
install_products
(
app
):
# Install a list of products into the basic folder class, so
# that all folders know about top-level objects, aka products
path_join
=
os
.
path
.
join
isdir
=
os
.
path
.
isdir
exists
=
os
.
path
.
exists
DictType
=
type
({})
from
Folder
import
Folder
folder_permissions
=
{}
for
p
in
Folder
.
__ac_permissions__
:
permission
,
names
=
p
[:
2
]
folder_permissions
[
permission
]
=
names
folder_permissions
=
get_folder_permissions
()
meta_types
=
[]
global_dict
=
globals
()
silly
=
(
'__doc__'
,)
done
=
{}
get_transaction
().
note
(
'Prior to product installs'
)
...
...
@@ -579,13 +577,38 @@ def install_products(app):
if
done
.
has_key
(
product_name
):
continue
done
[
product_name
]
=
1
install_product
(
app
,
product_dir
,
product_name
,
meta_types
,
folder_permissions
)
Products
.
meta_types
=
Products
.
meta_types
+
tuple
(
meta_types
)
Globals
.
default__class_init__
(
Folder
.
Folder
)
def
get_folder_permissions
():
folder_permissions
=
{}
for
p
in
Folder
.
Folder
.
__ac_permissions__
:
permission
,
names
=
p
[:
2
]
folder_permissions
[
permission
]
=
names
return
folder_permissions
def
install_product
(
app
,
product_dir
,
product_name
,
meta_types
,
folder_permissions
,
raise_exc
=
0
,
log_exc
=
1
):
path_join
=
os
.
path
.
join
isdir
=
os
.
path
.
isdir
exists
=
os
.
path
.
exists
DictType
=
type
({})
global_dict
=
globals
()
silly
=
(
'__doc__'
,)
if
1
:
# Preserve indentation for diff :-)
package_dir
=
path_join
(
product_dir
,
product_name
)
__traceback_info__
=
product_name
if
not
isdir
(
package_dir
):
continue
if
not
isdir
(
package_dir
):
return
if
not
exists
(
path_join
(
package_dir
,
'__init__.py'
)):
if
not
exists
(
path_join
(
package_dir
,
'__init__.pyc'
)):
continue
return
try
:
product
=
__import__
(
"Products.%s"
%
product_name
,
global_dict
,
global_dict
,
silly
)
...
...
@@ -605,7 +628,7 @@ def install_products(app):
# should be associated with that product. Products are
# expected to implement a method named 'initialize' in
# their __init__.py that takes the ProductContext as an
# argument.
# argument.
productObject
=
App
.
Product
.
initializeProduct
(
product
,
product_name
,
package_dir
,
app
)
context
=
ProductContext
(
productObject
,
app
,
product
)
...
...
@@ -616,7 +639,7 @@ def install_products(app):
# build up enough information to do initialization manually.
initmethod
=
pgetattr
(
product
,
'initialize'
,
None
)
if
initmethod
is
not
None
:
initmethod
(
context
)
initmethod
(
context
)
# Support old-style product metadata. Older products may
# define attributes to name their permissions, meta_types,
...
...
@@ -661,8 +684,8 @@ def install_products(app):
for
permission
,
names
in
new_permissions
:
folder_permissions
[
permission
]
=
names
new_permissions
.
sort
()
Folder
.
__dict__
[
'__ac_permissions__'
]
=
tuple
(
list
(
Folder
.
__ac_permissions__
)
+
new_permissions
)
Folder
.
Folder
.
__dict__
[
'__ac_permissions__'
]
=
tuple
(
list
(
Folder
.
Folder
.
__ac_permissions__
)
+
new_permissions
)
if
(
os
.
environ
.
get
(
'ZEO_CLIENT'
)
and
not
os
.
environ
.
get
(
'FORCE_PRODUCT_LOAD'
)):
...
...
@@ -674,13 +697,58 @@ def install_products(app):
get_transaction
().
commit
()
except
:
LOG
(
'Zope'
,
ERROR
,
'Couldn
\
'
t install %s'
%
product_name
,
error
=
sys
.
exc_info
())
if
log_exc
:
LOG
(
'Zope'
,
ERROR
,
'Couldn
\
'
t install %s'
%
product_name
,
error
=
sys
.
exc_info
())
get_transaction
().
abort
()
if
raise_exc
:
raise
def
reinstall_product
(
app
,
product_name
):
folder_permissions
=
get_folder_permissions
()
meta_types
=
[]
get_transaction
().
note
(
'Prior to product reinstall'
)
get_transaction
().
commit
()
for
product_dir
in
Products
.
__path__
:
product_names
=
os
.
listdir
(
product_dir
)
product_names
.
sort
()
if
product_name
in
product_names
:
removeProductMetaTypes
(
product_name
)
install_product
(
app
,
product_dir
,
product_name
,
meta_types
,
folder_permissions
,
raise_exc
=
1
,
log_exc
=
0
)
Products
.
meta_types
=
Products
.
meta_types
+
tuple
(
meta_types
)
Globals
.
default__class_init__
(
Folder
.
Folder
)
Globals
.
default__class_init__
(
Folder
)
def
reimport_product
(
product_name
):
for
product_dir
in
Products
.
__path__
:
product_names
=
os
.
listdir
(
product_dir
)
product_names
.
sort
()
if
product_name
in
product_names
:
import_product
(
product_dir
,
product_name
,
raise_exc
=
1
,
log_exc
=
0
)
break
def
removeProductMetaTypes
(
pid
):
'''
Unregisters the meta types registered by a product.
'''
meta_types
=
Products
.
meta_types
new_mts
=
[]
changed
=
0
for
meta_type
in
meta_types
:
if
meta_type
.
get
(
'product'
,
None
)
==
pid
:
# Remove this meta type.
changed
=
1
else
:
new_mts
.
append
(
meta_type
)
if
changed
:
Products
.
meta_types
=
tuple
(
new_mts
)
def
pgetattr
(
product
,
name
,
default
=
install_products
,
__init__
=
0
):
...
...
lib/python/Products/OFSP/help/Product_Refresh.stx
0 → 100644
View file @
d8eaafb3
Product - Refresh: Reload a filesystem-based Python product.
Description
This view allows you to reload filesystem-based product code
without restarting Zope. This function is useful during
development of products.
To enable your product to be refreshed, it is required that you
put a file called 'refresh.txt' in your product directory.
It can optionally contain a warning for others who might use
the refresh function.
(Producting refreshing is not perfect. Some products, especially
"hotfix" style products which patch Zope, should not be refreshed.
That's why 'refresh.txt' is required. Most products are safe to
refresh, however.)
There are two modes of operation. You can visit your product's
'Refresh' tab and manually push the refresh button. Or you can
turn on "auto-refresh" mode, which causes Zope to periodically
scan the modification time of the Python files that make up your
product and execute a refresh operation in the background.
**NOTE**: Don't enable auto-refresh for too many products at once.
Scanning file modification times can take a lot of time per
request.
You can also select dependent refreshable products. If you have
a product that subclasses from a product you're working on,
you'll want to enable refresh for both products and add the
product that subclasses as a dependent of the product you're
modifying. This enables subclasses to be updated.
Controls
'Refresh this product' -- The manual refresh button.
'Auto refresh mode' -- Check the checkbox to enable auto-refresh.
'Dependent auto-refreshable products' -- A list of other products
which are auto-refreshable.
How it works
To execute a refresh, Zope looks in the sys.modules dictionary
for modules with names that start with the prefix for your product.
It tries to scan for dependencies between the modules that make
up your product then uses Python's reload() function for each
module in order. Then it sets a flag that will cause ZODB to dump
its cache on the next connection so that changes to persistent
classes will take effect.
To implement auto-refresh, Zope stores a PersistentMapping called
RefreshData on the database root object (below the Application
object). The contents of the PersistentMapping are examined at the
moment a database connection is opened by ZApplication. The
PersistentMapping contains a list of which products have auto-refresh
enabled. For each product with auto-refresh enabled, Zope compares
the file mod times with the last recorded times and executes a
refresh if there are any changes.
lib/python/Zope/__init__.py
View file @
d8eaafb3
...
...
@@ -142,13 +142,18 @@ app=bobo_application=ZODB.ZApplication.ZApplicationWrapper(
# Initialize products:
c
=
app
()
OFS
.
Application
.
initialize
(
c
)
if
Globals
.
DevelopmentMode
:
# Set up auto-refresh.
from
App.RefreshFuncs
import
setupAutoRefresh
setupAutoRefresh
(
c
.
_p_jar
)
c
.
_p_jar
.
close
()
del
c
# "Log off" as system user
AccessControl
.
SecurityManagement
.
noSecurityManager
()
# This is sneaky, but we don't want to play with Main:
sys
.
modules
[
'Main'
]
=
sys
.
modules
[
'Zope'
]
...
...
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