티스토리 뷰



협업 개발 시스템인 Trac을 분석하기 위해서는 먼저 "협업 개발 시스템 Trac 수정을 위한 개발 환경 준비하기"를 통해 개발 환경을 준비해야 합니다. 


프로그램 분석 과정에서 상당히 중요한 부분 중의 하나가 바로 프로그램의 시작점을 찾는 것입니다. Trac 프로젝트의 프로그램의 시작점은 크게 2가지로 나뉘는데 하나는 trac-admin이고 다른 하나는 웹 인터페이스를 관장하는 tracd 입니다. Trac의 핵심 기능은 웹서버나 tracd를 통해서 플러그인 형태로 제공되는데 관련 정보는 설치 코드 위치의 Trac.egg-info\entry_points.txt에 있으며 그 내용은 아래와 같습니다.


        [console_scripts]

        trac-admin = trac.admin.console:run

        tracd = trac.web.standalone:main



        [trac.plugins]

        trac.about = trac.about

        trac.admin.console = trac.admin.console

        trac.admin.web_ui = trac.admin.web_ui

        trac.attachment = trac.attachment

        trac.db.mysql = trac.db.mysql_backend

        trac.db.postgres = trac.db.postgres_backend

        trac.db.sqlite = trac.db.sqlite_backend

        trac.mimeview.patch = trac.mimeview.patch

        trac.mimeview.pygments = trac.mimeview.pygments[Pygments]

        trac.mimeview.rst = trac.mimeview.rst[reST]

        trac.mimeview.txtl = trac.mimeview.txtl[Textile]

        trac.prefs = trac.prefs.web_ui

        trac.search = trac.search.web_ui

        trac.ticket.admin = trac.ticket.admin

        trac.ticket.batch = trac.ticket.batch

        trac.ticket.query = trac.ticket.query

        trac.ticket.report = trac.ticket.report

        trac.ticket.roadmap = trac.ticket.roadmap

        trac.ticket.web_ui = trac.ticket.web_ui

        trac.timeline = trac.timeline.web_ui

        trac.versioncontrol.admin = trac.versioncontrol.admin

        trac.versioncontrol.svn_authz = trac.versioncontrol.svn_authz

        trac.versioncontrol.web_ui = trac.versioncontrol.web_ui

        trac.web.auth = trac.web.auth

        trac.web.session = trac.web.session

        trac.wiki.admin = trac.wiki.admin

        trac.wiki.interwiki = trac.wiki.interwiki

        trac.wiki.macros = trac.wiki.macros

        trac.wiki.web_ui = trac.wiki.web_ui

        trac.wiki.web_api = trac.wiki.web_api

        tracopt.mimeview.enscript = tracopt.mimeview.enscript

        tracopt.mimeview.php = tracopt.mimeview.php

        tracopt.mimeview.silvercity = tracopt.mimeview.silvercity[SilverCity]

        tracopt.perm.authz_policy = tracopt.perm.authz_policy

        tracopt.perm.config_perm_provider = tracopt.perm.config_perm_provider

        tracopt.ticket.clone = tracopt.ticket.clone

        tracopt.ticket.commit_updater = tracopt.ticket.commit_updater

        tracopt.ticket.deleter = tracopt.ticket.deleter

        tracopt.versioncontrol.git.git_fs = tracopt.versioncontrol.git.git_fs

        tracopt.versioncontrol.svn.svn_fs = tracopt.versioncontrol.svn.svn_fs

        tracopt.versioncontrol.svn.svn_prop = tracopt.versioncontrol.svn.svn_prop


tracd의 시작점인 trac\web\standalone.py의 main() 함수는 전달된 옵션을 파싱하고 옵션에 따라 내장 웹서버를 가동 시키는 형태 입니다. 아래는 main()함수의 일부 입니다.


        def serve():
            addr, port = server_address
            if not addr or addr == '0.0.0.0':
                loc = '0.0.0.0:%s view at http://127.0.0.1:%s/%s' \
                       % (port, port, base_path)
            else:
                loc = 'http://%s:%s/%s' % (addr, port, base_path)


            try:
                httpd = TracHTTPServer(server_address, wsgi_app,
                                       options.env_parent_dir, args,
                                       use_http_11=options.http11)
            except socket.error, e:
                print 'Error starting Trac server on %s' % loc
                print '[Errno %s] %s' % e.args
                sys.exit(1)


            print 'Server starting in PID %i.' % os.getpid()
            print 'Serving on %s' % loc
            if options.http11:
                print 'Using HTTP/1.1 protocol version'
            httpd.serve_forever()


tracd의 종료 시점까지 웹으로 부터 전달되는 각종 요청은 trac\web\main.py의 dispatch_request()함수에 의해 처리 됩니다. dispatch_request()에서는 우선 "http://127.0.0.1:8000/devtest/wiki"와 같은 방식으로 전달된 URL을 분석하여 어떤 프로젝트에 접근 하려는지를 찾아(예제 URL에서는 devtest) 프로젝트 환경을 로드 합니다. 프로젝트 환경은 아래의 그림과 같이 프로젝트 단위의 폴더에 환경파일(trac.ini), 데이터베이스, 프로젝트 단위의 웹리소스(이미지, 문서등)와 플러그인 및 템플릿, 위키와 티켓의 첨부 파일로 구성 됩니다.



URL에 프로젝트를 지정하지 않은 경우에는 전체 프로젝트 목록 정보를 main.py에 있는 send_project_index() 함수를 통해서 출력 합니다. send_project_index()는 trac\templates 폴더에 env_index_template이 환경에 있다면 env_index_template을 템플릿으로 사용하고 없다면 템플릿 폴더의 index.html을(기본값) 템플릿으로 사용 합니다.

 

프로젝트 환경을 로드한 다음 _dispatch_request()함수와 RequestDispatcher 클래스의 dispatch()를 통해서 웹 요청에 대한 적절한 핸들러를 할당하고 사용자 인증과 핸들러 전/후처리를 수행 합니다.

 

dispatch()에서는 IRequestHandler를 적용한 핸들러들을 URL 기준으로 검색하여 해당 웹 요청을 처리할 핸들러를 선택 합니다. IRequestHandler를 적용한 핸들러 모듈은 아래의 코드 예제 처럼 match_request 메쏘드를 통해서 자신이 처리할 URL 형태이면 True를 리턴 합니다.


class AboutModule(Component):
    """"About Trac" page provider, showing version information from
    third-party packages, as well as configuration information."""


    required = True


    implements(INavigationContributor, IPermissionRequestor, IRequestHandler)


    # INavigationContributor methods
    def get_active_navigation_item(self, req):

        return 'about'


    def get_navigation_items(self, req):
        yield ('metanav', 'about',
               tag.a(_('About Trac'), href=req.href.about()))


    # IPermissionRequestor methods
    def get_permission_actions(self):

        return ['CONFIG_VIEW']


    # IRequestHandler methods
    def match_request(self, req):

        return re.match(r'/about(?:_trac)?(?:/.+)?$', req.path_info)



    def process_request(self, req):
        data = {'systeminfo': None, 'plugins': None, 'config': None}
......


핸들러 모듈의 match_request() 메쏘드에서 자신이 처리해야할 URL이어서 True를 리턴했다면 실제 처리를 담당하는 메쏘드는 핸들러 모듈에 정의한 process_request() 메쏘드 입니다. 위의 코드를 보면 핸들러별로 get_permission_actions() 메쏘드를 통해서 핸들러를 수행하기 위한 권한을 지정해주는데 다음은 핸들러 모듈 별로 URL 예제와 필요한 권한을 표시한 것입니다.(핸들러별 URL 예제는 앞부분의 프로토콜 및 프로젝트 이름을 제외한 것입니다)

  • AboutModule(trac/about.py) : Trac 소개 페이지 출력
    "/about" 또는 "/about_trac"   CONFIG_VIEW 권한
  • AttachmentModule(trac/attachment.py) : 첨부 파일 처리
    "/attachment/ticket/티켓번호" 또는 "/attachment/wiki/위키이름", "/attachment" 대신 "raw-attachment"이 사용될 수 있고 "zip-attachment"로 요청되면 첨부를 압축해서 내려받을 수 있도록 합니다.
  • AdminModule(trac/admin/web_ui.py) : 관리자 페이지
    "/admin"   TRAC_ADMIN 권한
  • PygmentsRenderer(trac/mimeview/pygments.py): 파이썬 문법 표시기(http://pygments.org/)
    "/pygments/*.css"
  • PreferencesModule(trac/prefs/web_ui.py) : 개인 설정 관리
    "/prefs"
  • SearchModule(trac/search/web_ui.py) : 검색
    "/search", "/search/opensearch"    SEARCH_VIEW 권한
  • BatchModifyModule(trac/ticket/batch.py) : 티켓 일괄 변경
    "/batchmodify"   TICKET_BATCH_MODIFY 권한
  • QueryModule(trac/ticket/query.py) : 티켓 검색
    "/query"   TICKET_VIEW 권한
  • ReportModule(trac/ticket/report.py) : 티켓 보고서
    "/report/"   REPORT_ADMIN, REPORT_CREATE, REPORT_DELETE, REPORT_MODIFY, REPORT_SQL_VIEW, REPORT_VIEW 권한
  • RoadmapModule(trac/ticket/roadmap.py) : 마일스톤 대시보드
    "/roadmap"   ROADMAP_ADMIN, MILESTONE_CREATE, MILESTONE_DELETE, MILESTONE_MODIFY, MILESTONE_VIEW, ROADMAP_VIEW 권한
  • MilestoneModule(trac/ticket/roadmap.py) : 마일스톤 조회 및 편집
    "/milestone"  MILESTONE_ADMIN, MILESTONE_CREATE, MILESTONE_DELETE, MILESTONE_MODIFY, MILESTONE_VIEW 권한
  • TicketModule(trac/ticket/web_ui.py) : 티켓 추가 및 편집
    "/newticket", "/ticket/티켓번호"   TICKET_VIEW, TICKET_CREATE, TICKET_EDIT_COMMENT, TICKET_CHGPROP, TICKET_APPEND, TICKET_MODIFY 권한
  • TimelineModule(trac/timeline/web_ui.py) : 프로젝트 히스토리
    "/timeline"   TIMELINE_VIEW 권한
  • LoginModule(trac/web/auth.py) : 로그인, 로그아웃
    "/login", "/logout"  
  • Chrome(trac/web/chrome.py) : 웹 관리자
    "/chrome/site/파일명" 프로젝트 환경의 htdocs폴더, "/chrome/common/파일명" Trac코드/trac/htdocs폴더, "/chrome/shared/파일명" 프로젝트간 공유 폴더(trac.init>[inherit]>htdocs_dir로 지정)
  • InterTracDispatcher(trac/wiki/intertrac.py) : Trac간 연동
    "/intertrac/경로"
  • WikiRenderer(trac/wiki/web_api.py) : 위키 텍스트 표시기
    "/wiki_render"
  • WikiModule(trac/wiki/web_ui.py) : 위키 모듈
    "/wiki/"   WIKI_ADMIN, WIKI_CREATE, WIKI_DELETE, WIKI_MODIFY, WIKI_RENAME, WIKI_VIEW
  • BrowserModule(trac/versioncontrol/web_ui/browser.py) : 서브버전 브라우저
    "/export/파일명", "/browser/파일명", "/file/파일명"   BROWSER_VIEW, FILE_VIEW 권한
  • ChangesetModule(trac/versioncontrol/web_ui/changeset.py) : 서브버전 변동사항
    "/changeset/"   CHANGESET_VIEW 권한
  • LogModule(trac/versioncontrol/web_ui/log.py) : 서브버전 로그
    "/log/"   LOG_VIEW 권한

URL이 프로젝트 홈을 지정한 경우에는 기본 핸들러는 WikiModule이 적용되고 WikiStart 페이지를 출력 합니다.
 
웹 요청에 대하여 각 핸들러의 실제 작업은 process_request() 메쏘드에서 처리되는데 아래 코드 예제 처럼 메쏘드 맨 끝에 출력을 위한 템플릿 이름과 조립에 사용할 데이터를 넘겨 줍니다.

    def process_request(self, req):
        data = {'systeminfo': None, 'plugins': None, 'config': None}


        if 'CONFIG_VIEW' in req.perm('config', 'systeminfo'):
            # Collect system information
            data['systeminfo'] = self.env.get_systeminfo()


        if 'CONFIG_VIEW' in req.perm('config', 'plugins'):
            # Collect plugin information
            data['plugins'] = get_plugin_info(self.env)


        if 'CONFIG_VIEW' in req.perm('config', 'ini'):
            # Collect config information
            defaults = self.config.defaults(self.compmgr)
            sections = []
            for section in self.config.sections(self.compmgr):
                options = []
                default_options = defaults.get(section, {})
                for name, value in self.config.options(section, self.compmgr):
                    default = default_options.get(name) or ''
                    options.append({
                        'name': name, 'value': value,
                        'modified': unicode(value) != unicode(default)
                    })
                options.sort(key=lambda o: o['name'])
                sections.append({'name': section, 'options': options})
            sections.sort(key=lambda s: s['name'])
            data['config'] = sections


        return 'about.html', data, None


■ Trac의 템플릿


웹 요청을 처리하는 핸들러 메쏘드 process_request()의 결과는 필요한 템플릿과 데이터로 아래는 Trac에서 사용되고 있는 템플릿의 구조를 간단하게 나타낸 것입니다.


 * trac
	* templates 기본 템플릿
		about.html
		attachment.html
		attach_file_form.html
		author_or_creator.rss
		diff_div.html
		diff_options.html
		diff_view.html
		error.html
		history_view.html
		index.html
		layout.html
		list_of_attachments.html
		macros.html
		macros.rss
		page_index.html
		preview_file.html
		progress_bar.html
		progress_bar_grouped.html
		theme.html
	* admin
		templates 관리자 템플릿
			admin.html
			admin_basics.html
			admin_components.html
			admin_enums.html
			admin_legacy.html
			admin_logging.html
			admin_milestones.html
			admin_perms.html
			admin_plugins.html
			admin_versions.html
			deploy_trac.cgi
			deploy_trac.fcgi
			deploy_trac.wsgi
	* prefs
		templates 설정 템플릿
			prefs.html
			prefs_advanced.html
			prefs_datetime.html
			prefs_general.html
			prefs_keybindings.html
			prefs_language.html
			prefs_pygments.html
			prefs_userinterface.html
	* search
		templates 검색 템플릿
			opensearch.xml
			search.html
	* ticket
		templates 티켓 템플릿
			batch_modify.html
			batch_ticket_notify_email.txt
			milestone_delete.html
			milestone_edit.html
			milestone_view.html
			query.html
			query.rss
			query_results.html
			report.rss
			report_delete.html
			report_edit.html
			report_list.html
			report_list.rss
			report_view.html
			roadmap.html
			ticket.html
			ticket.rss
			ticket_box.html
			ticket_change.html
			ticket_notify_email.txt
			ticket_preview.html
	* timeline
		templates 프로젝트 기록 템플릿
			timeline.html
			timeline.rss
	* versioncontrol
		templates 형상관리 템플릿
			admin_repositories.html
			browser.html
			changeset.html
			diff_form.html
			dirlist_thead.html
			dir_entries.html
			path_links.html
			repository_index.html
			revisionlog.html
			revisionlog.rss
			revisionlog.txt
			sortable_th.html
	* wiki
		templates 기본 템플릿
			wiki_delete.html
			wiki_diff.html
			wiki_edit.html
			wiki_edit_form.html
			wiki_page_path.html
			wiki_rename.html
			wiki_view.html




■ Trac 데이터베이스


아래는 Trac 데이터베이스의 테이블 상세 내용 입니다. 여러 프로젝트를 별도의 Trac으로 관리하고 싶다면 프로젝트 단위로 데이터베이스를 생성해야 합니다. 




========== attachment ==========
+-------------+------------+------+-----+---------+-------+
| Field       | Type       | Null | Key | Default | Extra |
+-------------+------------+------+-----+---------+-------+
| type        | text       | NO   | PRI | NULL    |       |
| id          | text       | NO   | PRI | NULL    |       |
| filename    | text       | NO   | PRI | NULL    |       |
| size        | int(11)    | YES  |     | NULL    |       |
| time        | bigint(20) | YES  |     | NULL    |       |
| description | text       | YES  |     | NULL    |       |
| author      | text       | YES  |     | NULL    |       |
| ipnr        | text       | YES  |     | NULL    |       |
+-------------+------------+------+-----+---------+-------+

========== auth_cookie ==========
+--------+---------+------+-----+---------+-------+
| Field  | Type    | Null | Key | Default | Extra |
+--------+---------+------+-----+---------+-------+
| cookie | text    | NO   | PRI | NULL    |       |
| name   | text    | NO   | PRI | NULL    |       |
| ipnr   | text    | NO   | PRI | NULL    |       |
| time   | int(11) | YES  |     | NULL    |       |
+--------+---------+------+-----+---------+-------+

========== cache ==========
+------------+---------+------+-----+---------+-------+
| Field      | Type    | Null | Key | Default | Extra |
+------------+---------+------+-----+---------+-------+
| id         | int(11) | NO   | PRI | 0       |       |
| generation | int(11) | YES  |     | NULL    |       |
| key        | text    | YES  |     | NULL    |       |
+------------+---------+------+-----+---------+-------+

========== component ==========
+-------------+------+------+-----+---------+-------+
| Field       | Type | Null | Key | Default | Extra |
+-------------+------+------+-----+---------+-------+
| name        | text | NO   | PRI | NULL    |       |
| owner       | text | YES  |     | NULL    |       |
| description | text | YES  |     | NULL    |       |
+-------------+------+------+-----+---------+-------+

========== enum ==========
+-------+------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------+------+-----+---------+-------+
| type  | text | NO   | PRI | NULL    |       |
| name  | text | NO   | PRI | NULL    |       |
| value | text | YES  |     | NULL    |       |
+-------+------+------+-----+---------+-------+

========== milestone ==========
+-------------+------------+------+-----+---------+-------+
| Field       | Type       | Null | Key | Default | Extra |
+-------------+------------+------+-----+---------+-------+
| name        | text       | NO   | PRI | NULL    |       |
| due         | bigint(20) | YES  |     | NULL    |       |
| completed   | bigint(20) | YES  |     | NULL    |       |
| description | text       | YES  |     | NULL    |       |
+-------------+------------+------+-----+---------+-------+

========== node_change ==========
+-------------+---------+------+-----+---------+-------+
| Field       | Type    | Null | Key | Default | Extra |
+-------------+---------+------+-----+---------+-------+
| repos       | int(11) | NO   | PRI | 0       |       |
| rev         | text    | NO   | PRI | NULL    |       |
| path        | text    | NO   | PRI | NULL    |       |
| node_type   | text    | YES  |     | NULL    |       |
| change_type | text    | NO   | PRI | NULL    |       |
| base_path   | text    | YES  |     | NULL    |       |
| base_rev    | text    | YES  |     | NULL    |       |
+-------------+---------+------+-----+---------+-------+

========== permission ==========
+----------+------+------+-----+---------+-------+
| Field    | Type | Null | Key | Default | Extra |
+----------+------+------+-----+---------+-------+
| username | text | NO   | PRI | NULL    |       |
| action   | text | NO   | PRI | NULL    |       |
+----------+------+------+-----+---------+-------+

========== report ==========
+-------------+------------------+------+-----+---------+----------------+
| Field       | Type             | Null | Key | Default | Extra          |
+-------------+------------------+------+-----+---------+----------------+
| id          | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| author      | text             | YES  |     | NULL    |                |
| title       | text             | YES  |     | NULL    |                |
| query       | text             | YES  |     | NULL    |                |
| description | text             | YES  |     | NULL    |                |
+-------------+------------------+------+-----+---------+----------------+

========== repository ==========
+-------+---------+------+-----+---------+-------+
| Field | Type    | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id    | int(11) | NO   | PRI | 0       |       |
| name  | text    | NO   | PRI | NULL    |       |
| value | text    | YES  |     | NULL    |       |
+-------+---------+------+-----+---------+-------+

========== revision ==========
+---------+------------+------+-----+---------+-------+
| Field   | Type       | Null | Key | Default | Extra |
+---------+------------+------+-----+---------+-------+
| repos   | int(11)    | NO   | PRI | 0       |       |
| rev     | text       | NO   | PRI | NULL    |       |
| time    | bigint(20) | YES  |     | NULL    |       |
| author  | text       | YES  |     | NULL    |       |
| message | text       | YES  |     | NULL    |       |
+---------+------------+------+-----+---------+-------+

========== session ==========
+---------------+---------+------+-----+---------+-------+
| Field         | Type    | Null | Key | Default | Extra |
+---------------+---------+------+-----+---------+-------+
| sid           | text    | NO   | PRI | NULL    |       |
| authenticated | int(11) | NO   | PRI | 0       |       |
| last_visit    | int(11) | YES  | MUL | NULL    |       |
+---------------+---------+------+-----+---------+-------+

========== session_attribute ==========
+---------------+---------+------+-----+---------+-------+
| Field         | Type    | Null | Key | Default | Extra |
+---------------+---------+------+-----+---------+-------+
| sid           | text    | NO   | PRI | NULL    |       |
| authenticated | int(11) | NO   | PRI | 0       |       |
| name          | text    | NO   | PRI | NULL    |       |
| value         | text    | YES  |     | NULL    |       |
+---------------+---------+------+-----+---------+-------+

========== system ==========
+-------+------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------+------+-----+---------+-------+
| name  | text | NO   | PRI | NULL    |       |
| value | text | YES  |     | NULL    |       |
+-------+------+------+-----+---------+-------+

========== ticket ==========
+-------------+------------------+------+-----+---------+----------------+
| Field       | Type             | Null | Key | Default | Extra          |
+-------------+------------------+------+-----+---------+----------------+
| id          | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| type        | text             | YES  |     | NULL    |                |
| time        | bigint(20)       | YES  | MUL | NULL    |                |
| changetime  | bigint(20)       | YES  |     | NULL    |                |
| component   | text             | YES  |     | NULL    |                |
| severity    | text             | YES  |     | NULL    |                |
| priority    | text             | YES  |     | NULL    |                |
| owner       | text             | YES  |     | NULL    |                |
| reporter    | text             | YES  |     | NULL    |                |
| cc          | text             | YES  |     | NULL    |                |
| version     | text             | YES  |     | NULL    |                |
| milestone   | text             | YES  |     | NULL    |                |
| status      | text             | YES  | MUL | NULL    |                |
| resolution  | text             | YES  |     | NULL    |                |
| summary     | text             | YES  |     | NULL    |                |
| description | text             | YES  |     | NULL    |                |
| keywords    | text             | YES  |     | NULL    |                |
+-------------+------------------+------+-----+---------+----------------+

========== ticket_change ==========
+----------+------------+------+-----+---------+-------+
| Field    | Type       | Null | Key | Default | Extra |
+----------+------------+------+-----+---------+-------+
| ticket   | int(11)    | NO   | PRI | 0       |       |
| time     | bigint(20) | NO   | PRI | 0       |       |
| author   | text       | YES  |     | NULL    |       |
| field    | text       | NO   | PRI | NULL    |       |
| oldvalue | text       | YES  |     | NULL    |       |
| newvalue | text       | YES  |     | NULL    |       |
+----------+------------+------+-----+---------+-------+

========== ticket_custom ==========
+--------+---------+------+-----+---------+-------+
| Field  | Type    | Null | Key | Default | Extra |
+--------+---------+------+-----+---------+-------+
| ticket | int(11) | NO   | PRI | 0       |       |
| name   | text    | NO   | PRI | NULL    |       |
| value  | text    | YES  |     | NULL    |       |
+--------+---------+------+-----+---------+-------+

========== version ==========
+-------------+------------+------+-----+---------+-------+
| Field       | Type       | Null | Key | Default | Extra |
+-------------+------------+------+-----+---------+-------+
| name        | text       | NO   | PRI | NULL    |       |
| time        | bigint(20) | YES  |     | NULL    |       |
| description | text       | YES  |     | NULL    |       |
+-------------+------------+------+-----+---------+-------+

========== wiki ==========
+----------+------------+------+-----+---------+-------+
| Field    | Type       | Null | Key | Default | Extra |
+----------+------------+------+-----+---------+-------+
| name     | text       | NO   | PRI | NULL    |       |
| version  | int(11)    | NO   | PRI | 0       |       |
| time     | bigint(20) | YES  | MUL | NULL    |       |
| author   | text       | YES  |     | NULL    |       |
| ipnr     | text       | YES  |     | NULL    |       |
| text     | text       | YES  |     | NULL    |       |
| comment  | text       | YES  |     | NULL    |       |
| readonly | int(11)    | YES  |     | NULL    |       |
+----------+------------+------+-----+---------+-------+
 




댓글
댓글쓰기 폼