2006-05-02 09:31:56 +08:00
from django import template , templatetags
from django . template import RequestContext
2005-07-13 09:25:57 +08:00
from django . conf import settings
2005-10-18 13:12:45 +08:00
from django . contrib . admin . views . decorators import staff_member_required
2006-05-02 09:31:56 +08:00
from django . db import models
from django . shortcuts import render_to_response
from django . core . exceptions import ImproperlyConfigured , ViewDoesNotExist
2008-07-22 11:24:09 +08:00
from django . http import Http404
2006-05-02 09:31:56 +08:00
from django . core import urlresolvers
2008-07-19 07:54:34 +08:00
from django . contrib . admindocs import utils
2006-05-02 09:31:56 +08:00
from django . contrib . sites . models import Site
2009-03-19 00:55:59 +08:00
from django . utils . importlib import import_module
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
from django . utils . translation import ugettext as _
2007-11-14 20:58:53 +08:00
from django . utils . safestring import mark_safe
2005-07-27 00:11:43 +08:00
import inspect , os , re
2005-07-16 01:09:01 +08:00
2005-07-13 09:25:57 +08:00
# Exclude methods starting with these strings from documentation
MODEL_METHODS_EXCLUDE = ( ' _ ' , ' add_ ' , ' delete ' , ' save ' , ' set_ ' )
2006-06-27 07:01:46 +08:00
class GenericSite ( object ) :
domain = ' example.com '
name = ' my site '
2008-08-30 03:29:16 +08:00
def get_root_path ( ) :
try :
2009-07-17 00:16:13 +08:00
return urlresolvers . reverse ( ' admin:index ' )
2008-08-30 03:29:16 +08:00
except urlresolvers . NoReverseMatch :
2009-07-17 00:16:13 +08:00
from django . contrib import admin
try :
return urlresolvers . reverse ( admin . site . root , args = [ ' ' ] )
except urlresolvers . NoReverseMatch :
return getattr ( settings , " ADMIN_SITE_ROOT_URL " , " /admin/ " )
2008-08-30 03:29:16 +08:00
2005-07-13 09:25:57 +08:00
def doc_index ( request ) :
2005-11-28 01:20:43 +08:00
if not utils . docutils_is_available :
2005-07-13 09:25:57 +08:00
return missing_docutils_page ( request )
2008-07-19 07:54:34 +08:00
return render_to_response ( ' admin_doc/index.html ' , {
2008-08-30 03:29:16 +08:00
' root_path ' : get_root_path ( ) ,
2008-07-19 07:54:34 +08:00
} , context_instance = RequestContext ( request ) )
2005-10-18 13:12:45 +08:00
doc_index = staff_member_required ( doc_index )
2005-07-16 01:09:01 +08:00
2005-07-13 09:25:57 +08:00
def bookmarklets ( request ) :
2008-08-30 03:29:16 +08:00
admin_root = get_root_path ( )
2006-05-02 09:31:56 +08:00
return render_to_response ( ' admin_doc/bookmarklets.html ' , {
2008-08-30 03:29:16 +08:00
' root_path ' : admin_root ,
2007-11-14 20:58:53 +08:00
' admin_url ' : mark_safe ( " %s :// %s %s " % ( request . is_secure ( ) and ' https ' or ' http ' , request . get_host ( ) , admin_root ) ) ,
2006-05-02 09:31:56 +08:00
} , context_instance = RequestContext ( request ) )
2005-10-18 13:12:45 +08:00
bookmarklets = staff_member_required ( bookmarklets )
2005-07-13 09:25:57 +08:00
def template_tag_index ( request ) :
2005-11-28 01:20:43 +08:00
if not utils . docutils_is_available :
2005-07-13 09:25:57 +08:00
return missing_docutils_page ( request )
2005-07-16 01:09:01 +08:00
2005-07-13 09:25:57 +08:00
load_all_installed_template_libraries ( )
tags = [ ]
2005-11-28 01:14:11 +08:00
for module_name , library in template . libraries . items ( ) :
for tag_name , tag_func in library . tags . items ( ) :
2005-11-28 01:20:43 +08:00
title , body , metadata = utils . parse_docstring ( tag_func . __doc__ )
2005-11-28 01:14:11 +08:00
if title :
2006-08-11 13:07:07 +08:00
title = utils . parse_rst ( title , ' tag ' , _ ( ' tag: ' ) + tag_name )
2005-11-28 01:14:11 +08:00
if body :
2006-08-11 13:07:07 +08:00
body = utils . parse_rst ( body , ' tag ' , _ ( ' tag: ' ) + tag_name )
2005-11-28 01:14:11 +08:00
for key in metadata :
2006-08-11 13:07:07 +08:00
metadata [ key ] = utils . parse_rst ( metadata [ key ] , ' tag ' , _ ( ' tag: ' ) + tag_name )
2005-11-28 01:14:11 +08:00
if library in template . builtins :
tag_library = None
else :
tag_library = module_name . split ( ' . ' ) [ - 1 ]
tags . append ( {
' name ' : tag_name ,
' title ' : title ,
' body ' : body ,
' meta ' : metadata ,
' library ' : tag_library ,
} )
2008-07-19 07:54:34 +08:00
return render_to_response ( ' admin_doc/template_tag_index.html ' , {
2008-08-30 03:29:16 +08:00
' root_path ' : get_root_path ( ) ,
2008-07-19 07:54:34 +08:00
' tags ' : tags
} , context_instance = RequestContext ( request ) )
2005-10-18 13:12:45 +08:00
template_tag_index = staff_member_required ( template_tag_index )
2005-07-13 09:25:57 +08:00
def template_filter_index ( request ) :
2005-11-28 01:20:43 +08:00
if not utils . docutils_is_available :
2005-07-13 09:25:57 +08:00
return missing_docutils_page ( request )
2005-07-16 01:09:01 +08:00
2005-07-13 09:25:57 +08:00
load_all_installed_template_libraries ( )
filters = [ ]
2005-11-28 01:14:11 +08:00
for module_name , library in template . libraries . items ( ) :
for filter_name , filter_func in library . filters . items ( ) :
2005-11-28 01:20:43 +08:00
title , body , metadata = utils . parse_docstring ( filter_func . __doc__ )
2005-11-28 01:14:11 +08:00
if title :
2006-08-11 13:07:07 +08:00
title = utils . parse_rst ( title , ' filter ' , _ ( ' filter: ' ) + filter_name )
2005-11-28 01:14:11 +08:00
if body :
2006-08-11 13:07:07 +08:00
body = utils . parse_rst ( body , ' filter ' , _ ( ' filter: ' ) + filter_name )
2005-11-28 01:14:11 +08:00
for key in metadata :
2006-08-11 13:07:07 +08:00
metadata [ key ] = utils . parse_rst ( metadata [ key ] , ' filter ' , _ ( ' filter: ' ) + filter_name )
2005-11-28 01:14:11 +08:00
if library in template . builtins :
tag_library = None
else :
tag_library = module_name . split ( ' . ' ) [ - 1 ]
filters . append ( {
' name ' : filter_name ,
' title ' : title ,
' body ' : body ,
' meta ' : metadata ,
' library ' : tag_library ,
} )
2008-07-19 07:54:34 +08:00
return render_to_response ( ' admin_doc/template_filter_index.html ' , {
2008-08-30 03:29:16 +08:00
' root_path ' : get_root_path ( ) ,
2008-07-19 07:54:34 +08:00
' filters ' : filters
} , context_instance = RequestContext ( request ) )
2005-10-18 13:12:45 +08:00
template_filter_index = staff_member_required ( template_filter_index )
2005-07-13 09:25:57 +08:00
def view_index ( request ) :
2005-11-28 01:20:43 +08:00
if not utils . docutils_is_available :
2005-07-13 09:25:57 +08:00
return missing_docutils_page ( request )
2006-05-02 09:31:56 +08:00
if settings . ADMIN_FOR :
2009-03-19 00:55:59 +08:00
settings_modules = [ import_module ( m ) for m in settings . ADMIN_FOR ]
2006-05-02 09:31:56 +08:00
else :
settings_modules = [ settings ]
2005-07-13 09:25:57 +08:00
views = [ ]
2006-05-02 09:31:56 +08:00
for settings_mod in settings_modules :
2009-03-19 00:55:59 +08:00
urlconf = import_module ( settings_mod . ROOT_URLCONF )
2005-07-13 09:25:57 +08:00
view_functions = extract_views_from_urlpatterns ( urlconf . urlpatterns )
2006-06-27 07:01:46 +08:00
if Site . _meta . installed :
site_obj = Site . objects . get ( pk = settings_mod . SITE_ID )
else :
site_obj = GenericSite ( )
2005-07-13 09:25:57 +08:00
for ( func , regex ) in view_functions :
views . append ( {
2009-04-02 02:44:25 +08:00
' name ' : getattr ( func , ' __name__ ' , func . __class__ . __name__ ) ,
2005-11-28 01:14:11 +08:00
' module ' : func . __module__ ,
2005-07-13 09:25:57 +08:00
' site_id ' : settings_mod . SITE_ID ,
2006-06-27 07:01:46 +08:00
' site ' : site_obj ,
2005-11-28 01:14:11 +08:00
' url ' : simplify_regex ( regex ) ,
2005-07-13 09:25:57 +08:00
} )
2008-07-19 07:54:34 +08:00
return render_to_response ( ' admin_doc/view_index.html ' , {
2008-08-30 03:29:16 +08:00
' root_path ' : get_root_path ( ) ,
2008-07-19 07:54:34 +08:00
' views ' : views
} , context_instance = RequestContext ( request ) )
2005-10-18 13:12:45 +08:00
view_index = staff_member_required ( view_index )
2005-07-13 09:25:57 +08:00
def view_detail ( request , view ) :
2005-11-28 01:20:43 +08:00
if not utils . docutils_is_available :
2005-07-13 09:25:57 +08:00
return missing_docutils_page ( request )
mod , func = urlresolvers . get_mod_func ( view )
try :
2009-03-19 00:55:59 +08:00
view_func = getattr ( import_module ( mod ) , func )
2005-07-13 09:25:57 +08:00
except ( ImportError , AttributeError ) :
raise Http404
2005-11-28 01:20:43 +08:00
title , body , metadata = utils . parse_docstring ( view_func . __doc__ )
2005-07-13 09:25:57 +08:00
if title :
2006-08-11 13:07:07 +08:00
title = utils . parse_rst ( title , ' view ' , _ ( ' view: ' ) + view )
2005-07-13 09:25:57 +08:00
if body :
2006-08-11 13:07:07 +08:00
body = utils . parse_rst ( body , ' view ' , _ ( ' view: ' ) + view )
2005-07-13 09:25:57 +08:00
for key in metadata :
2006-08-11 13:07:07 +08:00
metadata [ key ] = utils . parse_rst ( metadata [ key ] , ' model ' , _ ( ' view: ' ) + view )
2006-05-02 09:31:56 +08:00
return render_to_response ( ' admin_doc/view_detail.html ' , {
2008-08-30 03:29:16 +08:00
' root_path ' : get_root_path ( ) ,
2005-09-22 13:23:41 +08:00
' name ' : view ,
' summary ' : title ,
' body ' : body ,
' meta ' : metadata ,
2006-05-02 09:31:56 +08:00
} , context_instance = RequestContext ( request ) )
2005-10-18 13:12:45 +08:00
view_detail = staff_member_required ( view_detail )
2005-07-13 09:25:57 +08:00
def model_index ( request ) :
2005-11-28 01:20:43 +08:00
if not utils . docutils_is_available :
2005-07-13 09:25:57 +08:00
return missing_docutils_page ( request )
2006-05-02 09:31:56 +08:00
m_list = [ m . _meta for m in models . get_models ( ) ]
2008-07-19 07:54:34 +08:00
return render_to_response ( ' admin_doc/model_index.html ' , {
2008-08-30 03:29:16 +08:00
' root_path ' : get_root_path ( ) ,
2008-07-19 07:54:34 +08:00
' models ' : m_list
} , context_instance = RequestContext ( request ) )
2005-10-18 13:12:45 +08:00
model_index = staff_member_required ( model_index )
2005-07-13 09:25:57 +08:00
2006-05-02 09:31:56 +08:00
def model_detail ( request , app_label , model_name ) :
2005-11-28 01:20:43 +08:00
if not utils . docutils_is_available :
2005-07-13 09:25:57 +08:00
return missing_docutils_page ( request )
2009-07-17 00:16:13 +08:00
2006-05-02 09:31:56 +08:00
# Get the model class.
2005-07-13 09:25:57 +08:00
try :
2006-05-02 09:31:56 +08:00
app_mod = models . get_app ( app_label )
except ImproperlyConfigured :
2010-01-11 02:36:20 +08:00
raise Http404 ( _ ( " App %r not found " ) % app_label )
2006-05-02 09:31:56 +08:00
model = None
for m in models . get_models ( app_mod ) :
if m . _meta . object_name . lower ( ) == model_name :
model = m
break
if model is None :
2010-01-11 02:36:20 +08:00
raise Http404 ( _ ( " Model %(model_name)r not found in app %(app_label)r " ) % { ' model_name ' : model_name , ' app_label ' : app_label } )
2005-07-16 01:09:01 +08:00
2006-05-02 09:31:56 +08:00
opts = model . _meta
# Gather fields/field descriptions.
2005-07-13 09:25:57 +08:00
fields = [ ]
for field in opts . fields :
2006-05-02 09:31:56 +08:00
# ForeignKey is a special case since the field will actually be a
# descriptor that returns the other object
if isinstance ( field , models . ForeignKey ) :
data_type = related_object_name = field . rel . to . __name__
app_label = field . rel . to . _meta . app_label
2008-07-19 07:54:34 +08:00
verbose = utils . parse_rst ( ( _ ( " the related ` %(app_label)s . %(data_type)s ` object " ) % { ' app_label ' : app_label , ' data_type ' : data_type } ) , ' model ' , _ ( ' model: ' ) + data_type )
2006-05-02 09:31:56 +08:00
else :
data_type = get_readable_field_data_type ( field )
verbose = field . verbose_name
2005-07-13 09:25:57 +08:00
fields . append ( {
2005-11-28 01:14:11 +08:00
' name ' : field . name ,
2006-05-02 09:31:56 +08:00
' data_type ' : data_type ,
' verbose ' : verbose ,
2006-07-07 12:00:44 +08:00
' help_text ' : field . help_text ,
2005-07-13 09:25:57 +08:00
} )
2006-05-02 09:31:56 +08:00
2009-07-01 02:40:29 +08:00
# Gather many-to-many fields.
for field in opts . many_to_many :
data_type = related_object_name = field . rel . to . __name__
app_label = field . rel . to . _meta . app_label
verbose = _ ( " related ` %(app_label)s . %(object_name)s ` objects " ) % { ' app_label ' : app_label , ' object_name ' : data_type }
fields . append ( {
' name ' : " %s .all " % field . name ,
" data_type " : ' List ' ,
' verbose ' : utils . parse_rst ( _ ( " all %s " ) % verbose , ' model ' , _ ( ' model: ' ) + opts . module_name ) ,
} )
fields . append ( {
' name ' : " %s .count " % field . name ,
' data_type ' : ' Integer ' ,
' verbose ' : utils . parse_rst ( _ ( " number of %s " ) % verbose , ' model ' , _ ( ' model: ' ) + opts . module_name ) ,
} )
2006-05-02 09:31:56 +08:00
# Gather model methods.
for func_name , func in model . __dict__ . items ( ) :
if ( inspect . isfunction ( func ) and len ( inspect . getargspec ( func ) [ 0 ] ) == 1 ) :
2005-07-13 09:25:57 +08:00
try :
for exclude in MODEL_METHODS_EXCLUDE :
if func_name . startswith ( exclude ) :
raise StopIteration
except StopIteration :
continue
verbose = func . __doc__
if verbose :
2006-08-11 13:07:07 +08:00
verbose = utils . parse_rst ( utils . trim_docstring ( verbose ) , ' model ' , _ ( ' model: ' ) + opts . module_name )
2005-07-13 09:25:57 +08:00
fields . append ( {
2005-11-28 01:14:11 +08:00
' name ' : func_name ,
' data_type ' : get_return_data_type ( func_name ) ,
' verbose ' : verbose ,
2005-07-13 09:25:57 +08:00
} )
2006-05-02 09:31:56 +08:00
# Gather related objects
2009-07-01 02:40:29 +08:00
for rel in opts . get_all_related_objects ( ) + opts . get_all_related_many_to_many_objects ( ) :
2008-07-19 07:54:34 +08:00
verbose = _ ( " related ` %(app_label)s . %(object_name)s ` objects " ) % { ' app_label ' : rel . opts . app_label , ' object_name ' : rel . opts . object_name }
2006-05-02 09:31:56 +08:00
accessor = rel . get_accessor_name ( )
fields . append ( {
2006-05-18 23:17:42 +08:00
' name ' : " %s .all " % accessor ,
' data_type ' : ' List ' ,
2006-08-11 13:07:07 +08:00
' verbose ' : utils . parse_rst ( _ ( " all %s " ) % verbose , ' model ' , _ ( ' model: ' ) + opts . module_name ) ,
2006-05-02 09:31:56 +08:00
} )
fields . append ( {
2006-05-18 23:17:42 +08:00
' name ' : " %s .count " % accessor ,
' data_type ' : ' Integer ' ,
2006-08-11 13:07:07 +08:00
' verbose ' : utils . parse_rst ( _ ( " number of %s " ) % verbose , ' model ' , _ ( ' model: ' ) + opts . module_name ) ,
2006-05-02 09:31:56 +08:00
} )
return render_to_response ( ' admin_doc/model_detail.html ' , {
2008-08-30 03:29:16 +08:00
' root_path ' : get_root_path ( ) ,
2006-05-02 09:31:56 +08:00
' name ' : ' %s . %s ' % ( opts . app_label , opts . object_name ) ,
2006-08-18 11:37:01 +08:00
' summary ' : _ ( " Fields on %s objects " ) % opts . object_name ,
2006-02-28 05:37:11 +08:00
' description ' : model . __doc__ ,
2005-09-22 13:23:41 +08:00
' fields ' : fields ,
2006-05-02 09:31:56 +08:00
} , context_instance = RequestContext ( request ) )
2005-10-18 13:12:45 +08:00
model_detail = staff_member_required ( model_detail )
2005-07-13 09:25:57 +08:00
2005-08-03 04:29:27 +08:00
def template_detail ( request , template ) :
templates = [ ]
for site_settings_module in settings . ADMIN_FOR :
2009-03-19 00:55:59 +08:00
settings_mod = import_module ( site_settings_module )
2006-06-27 07:01:46 +08:00
if Site . _meta . installed :
site_obj = Site . objects . get ( pk = settings_mod . SITE_ID )
else :
site_obj = GenericSite ( )
2005-08-03 04:29:27 +08:00
for dir in settings_mod . TEMPLATE_DIRS :
2009-04-01 22:13:28 +08:00
template_file = os . path . join ( dir , template )
2005-08-03 04:29:27 +08:00
templates . append ( {
2005-11-28 01:14:11 +08:00
' file ' : template_file ,
' exists ' : os . path . exists ( template_file ) ,
' contents ' : lambda : os . path . exists ( template_file ) and open ( template_file ) . read ( ) or ' ' ,
' site_id ' : settings_mod . SITE_ID ,
2006-06-27 07:01:46 +08:00
' site ' : site_obj ,
2005-11-28 01:14:11 +08:00
' order ' : list ( settings_mod . TEMPLATE_DIRS ) . index ( dir ) ,
2005-08-03 04:29:27 +08:00
} )
2006-05-02 09:31:56 +08:00
return render_to_response ( ' admin_doc/template_detail.html ' , {
2008-08-30 03:29:16 +08:00
' root_path ' : get_root_path ( ) ,
2005-09-22 13:23:41 +08:00
' name ' : template ,
' templates ' : templates ,
2006-05-02 09:31:56 +08:00
} , context_instance = RequestContext ( request ) )
2005-10-18 13:12:45 +08:00
template_detail = staff_member_required ( template_detail )
2005-08-06 05:24:24 +08:00
2005-07-13 09:25:57 +08:00
####################
# Helper functions #
####################
def missing_docutils_page ( request ) :
""" Display an error message for people without docutils """
2006-05-02 09:31:56 +08:00
return render_to_response ( ' admin_doc/missing_docutils.html ' )
2005-07-13 09:25:57 +08:00
def load_all_installed_template_libraries ( ) :
2005-11-28 01:14:11 +08:00
# Load/register all template tag libraries from installed apps.
2010-01-26 09:38:50 +08:00
for module_name in template . get_templatetags_modules ( ) :
mod = import_module ( module_name )
libraries = [
os . path . splitext ( p ) [ 0 ]
for p in os . listdir ( os . path . dirname ( mod . __file__ ) )
if p . endswith ( ' .py ' ) and p [ 0 ] . isalpha ( )
]
2005-11-28 01:14:11 +08:00
for library_name in libraries :
2005-07-13 09:25:57 +08:00
try :
2010-01-26 09:38:50 +08:00
lib = template . get_library ( library_name )
except template . InvalidTemplateLibrary , e :
2005-07-13 09:25:57 +08:00
pass
2005-07-16 01:09:01 +08:00
2005-07-13 09:25:57 +08:00
def get_return_data_type ( func_name ) :
""" Return a somewhat-helpful data type given a function name """
if func_name . startswith ( ' get_ ' ) :
if func_name . endswith ( ' _list ' ) :
return ' List '
elif func_name . endswith ( ' _count ' ) :
return ' Integer '
return ' '
2005-07-16 01:09:01 +08:00
def get_readable_field_data_type ( field ) :
2009-12-17 02:13:34 +08:00
""" Returns the description for a given field type, if it exists,
Fields ' descriptions can contain format strings, which will be interpolated
against the values of field . __dict__ before being output . """
return field . description % field . __dict__
2005-07-13 09:25:57 +08:00
def extract_views_from_urlpatterns ( urlpatterns , base = ' ' ) :
"""
Return a list of views from a list of urlpatterns .
2005-07-16 01:09:01 +08:00
2005-07-13 09:25:57 +08:00
Each object in the returned list is a two - tuple : ( view_func , regex )
"""
views = [ ]
for p in urlpatterns :
2006-09-01 05:32:50 +08:00
if hasattr ( p , ' _get_callback ' ) :
2005-07-13 09:25:57 +08:00
try :
2006-09-01 05:32:50 +08:00
views . append ( ( p . _get_callback ( ) , base + p . regex . pattern ) )
2005-07-13 09:25:57 +08:00
except ViewDoesNotExist :
continue
2005-08-06 05:24:24 +08:00
elif hasattr ( p , ' _get_url_patterns ' ) :
2006-09-01 05:35:36 +08:00
try :
patterns = p . url_patterns
except ImportError :
continue
views . extend ( extract_views_from_urlpatterns ( patterns , base + p . regex . pattern ) )
2005-07-13 09:25:57 +08:00
else :
2010-01-11 02:36:20 +08:00
raise TypeError ( _ ( " %s does not appear to be a urlpattern object " ) % p )
2005-07-13 09:25:57 +08:00
return views
named_group_matcher = re . compile ( r ' \ ( \ ?P(< \ w+>).+? \ ) ' )
2006-05-02 09:31:56 +08:00
non_named_group_matcher = re . compile ( r ' \ (.*? \ ) ' )
2005-07-13 09:25:57 +08:00
def simplify_regex ( pattern ) :
2006-05-02 09:31:56 +08:00
"""
Clean up urlpattern regexes into something somewhat readable by Mere Humans :
turns something like " ^(?P<sport_slug> \ w+)/athletes/(?P<athlete_slug> \ w+)/$ "
into " <sport_slug>/athletes/<athlete_slug>/ "
"""
# handle named groups first
2005-07-13 09:25:57 +08:00
pattern = named_group_matcher . sub ( lambda m : m . group ( 1 ) , pattern )
2006-05-02 09:31:56 +08:00
# handle non-named groups
pattern = non_named_group_matcher . sub ( " <var> " , pattern )
# clean up any outstanding regex-y characters.
pattern = pattern . replace ( ' ^ ' , ' ' ) . replace ( ' $ ' , ' ' ) . replace ( ' ? ' , ' ' ) . replace ( ' // ' , ' / ' ) . replace ( ' \\ ' , ' ' )
2005-07-13 09:25:57 +08:00
if not pattern . startswith ( ' / ' ) :
pattern = ' / ' + pattern
2005-07-19 04:35:51 +08:00
return pattern