Tracでフォーム認証


Twitterを眺めていると、Tracの認証がログアウト出来ないとという話をよく見かけますが、AccountManagerプラグインを導入済みならば、AccountManagerプラグインのloginモジュールを使うと実現可能です。

以下は利用のための手順です。
※trunkのAccountManagerPluginを使うとハマるので、Stableのurlに訂正しました。

TracLightning3.1.1以前の3系及び、それ以外のTrac0.12環境の場合

1. 最新Stable版のAccountManagerプラグインをインストール

easy_install https://trac-hacks.org/svn/accountmanagerplugin/0.11

2. 最新版のXML-RPCプラグインを利用インストール

easy_install easy_install -Z -U http://trac-hacks.org/svn/xmlrpcplugin/trunk

3. 両プラグインを管理パネルもしくはtrac.iniを設定し利用可能にする。

4. AccountManagerプラグインXML-RPCプラグインが動作することを確認する。

5. LoginModule(trac.web.auth)のチェックボックスをoffに変更。
 

6. AccountManagerのLoginModule(acct_mgr.web_ui)のチェックボックスがonになっていることを確認。
  

7. httpd.confのTracに関する認証部分を変更
 tracのlogin関連のURLで認証するようになっている部分を、xmlrpc及びjsonrpcでのみ利用するように変更します。

#<LocationMatch "/[^/]+/login($|/)">
<LocationMatch "/[^/]+/login/(xmlrpc|rpc|jsonrpc)($|/)">
  AuthType Digest
  AuthName trac
  AuthUserFile "C:\TracLight\projects\trac.htdigest"
  Require valid-user
</LocationMatch>

8. Apacheを再起動し、"ログイン"をクリック
 
 フォームが出て以後はログインログアウトが可能になります。


TracLightning3.1.2alpha2以降の場合

まだアルファ版ですがTracLightning3.1.2以降ではプラグインを最新化し、もう少し簡単にフォーム認証が利用出来るようになる予定です。
現在のTracLightningをお使いでフォーム認証を試して見たいという人は、TracLightning3.1.2alpha2をこちらからダウンロードをして試してみると良いと思います。*1

1. LoginModule(trac.web.auth)のチェックボックスをoffに変更。
 

2. AccountManagerのLoginModule(acct_mgr.web_ui)のチェックボックスがonになっていることを確認。
  

3. Apachehttpd.confの509行目辺りからの認証設定変更
 xmlrpc及びjsonrpcでのみApacheの認証を利用するように変更します。

#<LocationMatch "/[^/]+/login($|/)">
<LocationMatch "/[^/]+/login/(xmlrpc|rpc|jsonrpc)($|/)">
  AuthType Digest
  AuthName trac
  AuthUserFile "C:\TracLight\projects\trac.htdigest"
  Require valid-user
</LocationMatch>

4. Apacheを再起動し、"ログイン"をクリック
 

以前に書いた不具合について

以前に『Trac Lightingでフォーム認証には問題がある』で書きましたが、TracLightning3.1.1以前に同梱されている、AccountManagerプラグインではdigest認証を利用したフォーム認証には不具合があります。またbasic認証を用いた場合でもXML-RPCが他のプラグインを組み合わせないと認証できない状態でした。

前述の通り、最新版のAccountManagerプラグインXML-RPCプラグインのloginモジュールを利用すると問題が解消しフォーム認証が利用できます。
Trac 0.11での動作検証は行っていませんが、0.11では0.12と同じプラグインを使うことからプラグインを最新化することにより対処できるかもしれません。0.11で運用中の方やTracLightningの2系を利用中の方で試した方のご報告お待ちしてます。

*1:リポジトリの構成を変える可能性があるためアルファ版扱いですが、プラグインの最新化が中心なので比較的安定しています。

石の上にも三年、Tracでも三年、じゃぁ・・・

すっかり遅くなりましたが以前に書いたとおり、5月21日(土)に行われた「DevLOVE HangarFlight - Spring Bomb -」でお話ししてきました。
DevLoveには初参加、初登壇w*1

ここ最近アジャイルな取り組みに魅せられていて職場でアジャイルな取り組みについて色々と話すのですが、なかなか理解されなかったり取り組んでもらえなかったりすることが少なくありません。
そんなとき折れないように「Tracでも三年」と心の中でつぶやいて頑張るようにしているのですが、その「Tracでも三年」の元となったTracへの取り組みについて、これまでShibuya.tracで発表した事例を元にお話ししてきました。



Twitterでのつぶやきや感想をみていると「Tracでも三年」だけは少なくても届いたようで安心。

正直な話、トップダウンで進めればもっと短期間で出来ると思います。
しかしトップダウンではなくボトムアップで取り組みだったので結果として長く掛かってます。
トップダウンにしてしまうとどうしても道具を使うことが目的となってしまい、道具を使う理由やそこから得られるメリットを考えなくなってしまいます。そこをどうしても避けたかったので、あえてボトムアップで取り組んだ結果、三年という月日が必要になってしまいました。

今一番関心のあるアジャイルな取り組みが当たり前になるには何年かかるんでしょうね(^^;;


今回、話の中でShibuya.tracとの出逢いや、そこで出会った仲間たちには言及しませんでしたが、非常に多くのものを得させて頂き、心の支えにもなりました。そして、それがあったからこそ今の自分があります。
貰ったものをほんの少しずつであっても還元できるように、少しでも誰かの役に立てるように頑張っていきたいと思います。

*1:そもそもLTを除いてShibuya.trac以外で話すのは初めてだった

0.12対応版チケットとカスタムクエリの日付表示フォーマットを変える

※2011/05/12 ticket_box.htmlのカスタマイズに誤りがあったので訂正しました。

以前にTrac 0.11用のカスタマイズ例を「チケットとカスタムクエリの日付表示フォーマットを変える - almost nearly dead」で書きましたが、Trac 0.12用についてもまとめておきます。

0.12では手を入れる対象のテンプレートは3つになります。
TracLightning3.1.1の場合、
%TRAC_LIGHT_HOME%\python-lib\trac\trac\ticket\templates以下に
ticket.html
ticket_box.html
query_results.html
がありますので、コピーしてから修正します。

--- ticket/templates/ticket.html
+++ mod/ticket.html
@@ -139,7 +139,7 @@
                         </py:for></py:if>
                     </py:if>
                   </span>
-                  <i18n:msg params="date, author">Changed ${dateinfo(change.date)} ago by ${authorinfo(change.author)}</i18n:msg>
+                  <i18n:msg params="date, author">Changed ${dateinfo(change.date)} ago by ${authorinfo(change.author)}</i18n:msg> ${format_datetime(ticket.changetime)}
                 </h3>
                 <py:if test="not show_editor and comment_version == max_version">
                   <form py:if="'cnum' in change and can_edit_comment" method="get" action="#comment:${change.cnum}">
@@ -162,11 +162,11 @@
                   <i18n:msg params="version, date, author" py:when="comment_version != max_version">
                       Version ${comment_version}, edited ${dateinfo(change.comment_history[comment_version].date)} ago
                       by ${authorinfo(change.comment_history[comment_version].author)}
-                  </i18n:msg>
+                  </i18n:msg> ${format_datetime(change.comment_history[comment_version].date)}
                   <i18n:msg params="date, author" py:otherwise="">
                       Last edited ${dateinfo(change.comment_history[comment_version].date)} ago
                       by ${authorinfo(change.comment_history[comment_version].author)}
-                  </i18n:msg>
+                  </i18n:msg> ${format_datetime(change.comment_history[comment_version].date)}
                   <py:if test="comment_version > 0">
                     (<a href="${href.ticket(ticket.id, cnum_hist=change.cnum, cversion=comment_version - 1)
                                }#comment:${change.cnum}">previous</a>)
--- ticket/templates/ticket_box.html
+++ mod/ticket_box.html
@@ -16,8 +16,8 @@
      xmlns:i18n="http://genshi.edgewall.org/i18n"
      id="ticket" class="${preview_mode and 'ticketdraft' or None}">
   <div class="date">
-    <p i18n:msg="created" py:if="ticket.exists">Opened ${dateinfo(ticket.time)} ago</p>
-    <p i18n:msg="modified" py:if="ticket.changetime != ticket.time">Last modified ${dateinfo(ticket.changetime)} ago</p>
+    <p><i18n:msg params="created" py:if="ticket.exists">Opened ${dateinfo(ticket.time)} ago</i18n:msg> (${format_datetime(ticket.time)})</p>
+    <p><i18n:msg params="modified" py:if="ticket.changetime != ticket.time">Last modified ${dateinfo(ticket.changetime)} ago</i18n:msg> (${format_datetime(ticket.changetime)})</p>
     <p py:if="not ticket.exists"><i>(ticket not yet created)</i></p>
   </div>
   <!--! use a placeholder if it's a new ticket -->
--- ticket/templates/query_results.html
+++ mod/query_results.html
@@ -73,7 +73,7 @@
                         class="${classes(closed=result.status == 'closed')}">#$result.id</a></td>
                     <td py:otherwise="" class="$name" py:choose="">
                       <a py:when="name == 'summary'" href="$result.href" title="View ticket">$value</a>
-                      <py:when test="isinstance(value, datetime)">${dateinfo(value)}</py:when>
+                      <py:when test="isinstance(value, datetime)">${format_date(value)} (${dateinfo(value)})</py:when>
                       <py:when test="name == 'reporter'">${authorinfo(value)}</py:when>
                       <py:when test="name == 'cc'">${format_emails(ticket_context, value)}</py:when>
                       <py:when test="name == 'owner' and value">${authorinfo(value)}</py:when>
@@ -88,8 +88,8 @@
               <py:with vars="result_rows = [t for t in row if result[t]]">
                 <tr py:if="result_rows" class="fullrow">
                   <td colspan="${len(headers)}">
-                    <p class="meta" i18n:msg="author, date">Reported by <strong>${authorinfo(result.reporter)}</strong>,
-                      ${dateinfo(result.time)} ago.</p>
+                    <p class="meta"><i18n:msg params="author, date">Reported by <strong>${authorinfo(result.reporter)}</strong>,
+                      ${dateinfo(result.time)} ago.</i18n:msg> (${format_datetime(result.time)})</p>
                   </td>
                 </tr>
                 <py:choose>

以前と同様に修正したテンプレートは、

  • プロジェクトのテンプレートフォルダに入れる。
    • %TRAC_LIGHT_HOME%\projects\trac\%プロジェクト名%\templates にカスタマイズしたファイルを置く
  • 利用しているTracの全体のテンプレートフォルダを作って、そこへ入れる。
    • %TRAC_LIGHT_HOME%\python\share\trac\conf\trac.iniの[inherit]のセクションにtemplates_dirを追加しテンプレートフォルダーを追加し、そのフォルダーにカスタマイズしたテンプレートを置く。

の、どちらかで対処しましょう。

プロジェクト個別で対応したければ前者、Trac全体に適用したいなら後者になると思います。

修正したテンプレートの移動が終わったら念のためApacheを再起動しましょう。
すると、若干表示位置は違いますが0.11のときと同じように日付が人に優しい形で表示されます。

チケットの登録/更新日
チケットのコメント
カスタムクエリ


同じような手法でリポジトリブラウザ関連の日付表示もカスタマイズできますが、0.12でテンプレートをカスタマイズする場合は、0.12では多国語対応(i18n)となっているためi18nがうまく機能するように手を入れてあげる必要があるので注意してください。
今回のカスタマイズもi18n対応を無視して0.11と同じ表示にすることも可能ですが、その場合はテンプレートの該当箇所を自力で日本語化する必要が出てきますのでご注意下さい。

前述の例は可能な限りTracが持っているi18nを生かした形でカスタマイズしてあります。

ちなみに次のバージョンである0.13では一歩進んだi18n対応が行われので、今までのような日付の表示周りのカスタマイズは不要になります。
参考:#9777 (New option for displaying absolute date/time in ticket) – The Trac Project

しゃべります

5月21日(土)に行われる「DevLOVE HangarFlight - Spring Bomb -」で喋ることになりました。

http://led.devlove.org/hangarflight/springbomb.jpg
http://led.devlove.org/hangarflight/springbomb_title.png

スライドを一枚も書いてませんがタイトルは「とある会社のtrac事情 〜不具合管理 からタスク管理へ〜 」ということで、不具合管理に思い悩んでいた時期からtracと出会い今に至るまでの話をする予定です。

並列するセッションが魅力的なものが多く自分が話すよりもそっちへ出たい感じで、人が入るか不安だったので

とつぶやいたところ
と担当の整備士からの反応を貰いましたので、人の入りが悪ければ悪いで面白いことになると思います。
なので間違ってw私のセッションを選んでも損はないと思います(笑)


DevLOVE HangarFlight - Spring Bomb -」への参加申し込みはこちらから こちらから

しゃべってました・・・

放置すること三ヶ月近くですが、この間に2度ほど喋ってきました。

ひとつは2月に行われたデブサミのコミュニティLTで、他の人たちがコミュニティの紹介をする中で一人だけ空気を読まず何かの宣言を行うという暴挙に出ました。
ブースも出させていただいたのですが、ブースの様子などはこちらをご覧下さい。
LTそのものはまぁ・・・緊張で酷い状態だったので触れないで頂けると嬉しいです(笑)




そして、もう一つはShibuya.trac第11回勉強会で可視化の事例の発表。
前回の発表からほぼ一年ぶりの真面目な発表で、現在自身が現在やっていることの話と、デモを中心に行いました。
オープンソースカンファレンス等でブース出したりしている割に、動かすことの出来るデモ環境などを用意してなかったのですが、今回の発表用に用意したので次にブース出したりする場合にはしっかりとデモが出来ますので、気になる方はお立ち寄り下さい。

ReportIncludeプラグインでReportをwikiに表示する(Reportサンプル2点)

(諸般の事情で下書きに眠っていたのを、諸般の事情が解決したので公開)
2011/3/3 SQLの修正と諸般の事情を追記

TracのReportやQueryは強力です。
TicketQueryマクロでQueryの結果をWikiに表示させることも可能ですが、SQLを使っているReportの表示をWIkiにすることは出来ません。「TracからRedmineへ移行しない、たった一つの理由」で以前に書いたとおり私がTracを使い続けている大きな理由になっているSQLを使った柔軟なレポートですが、Wikiに表示できないのでは魅力が半減してしまいます。


そこで使うのがReportIncludeプラグインです。
このプラグインの目玉は各種グラフをReportの結果から作成するところにあります。
そして注目されていないのですがSQLで書かれたReportの結果をWikiに表示することが可能です。


これを利用するとWikiにレポートの結果を表示させることが可能になるため、wiki記法が使える場所であればマイルストーンにもReportの説明の部分でもチケットにでも、レポートやグラフを表示できるようになります。Wikiに自由にレポートが載せられると強力な武器(?)になるので、TicketQueryでは物足りなかった人は試しては如何でしょうか?


以前に紹介したQueryChartと併せてマイルストーンwikiにレポートを上手く作って載せると進捗の把握に非常に便利になるので是非とも試してみてください。


と言われても、イメージが湧きにくいと思うので、TracLightningにも同梱されているTimingAndEstimationプラグインを使っている場合のサンプルイメージとレポートサンプルを用意しました。



レポートをマイルストーンに表示したイメージ

後で出てくる二つのレポートをマイルストーンの部分に表示しています。

マイルストーンwikiの部分に

[[ReportInclude(19?MLS=スプリント0(準備)&DH=7)]]
[[ReportInclude(20)]]

と書くと上記のようにレポートが表示されます。
(レポート番号やパラメータは適宜変更が必要です)

見積時間と実績時間及びチケット数を集計するレポート

対象マイルストーン(MLS)と、人日を計算するための時間(DH)をパラメータ化しています。

(0.11,0.12共用)2011/3/3書き換えました。

SELECT Hoge as ' ',
       TaskCount as タスク数,
       EstimateHour as 見積(時間),
       EstimateMD as 見積(人日),
       WorkHour as 実績(時間),
       WorkMD as 実績(人日),
       OverTaskCount as 超過数,
       OverTaskHour as 超過(時間),
       CASE Hoge
           WHEN '未着手' THEN 0
           WHEN '作業中' THEN 10
           WHEN '完了' THEN 90
           WHEN '合計' THEN 999
        END as _odr
FROM(
       SELECT
        CASE 
           WHEN status ='new' and tot.value = 0 THEN '未着手'
           WHEN status = 'closed' THEN '完了'
           ELSE '作業中'
        END as Hoge,
        COUNT(*) as TaskCount ,
        SUM(cast(est.value as REAL)) as EstimateHour ,
        ROUND(SUM(cast(est.value as REAL)/$DH),2) as EstimateMD ,
        SUM(cast(tot.value as REAL)) as WorkHour ,
        ROUND(SUM(cast(tot.value as REAL)/$DH),2) as WorkMD ,
        SUM(CASE WHEN cast(est.value as REAL) < cast(tot.value as REAL) THEN 1 ELSE 0 END) as OverTaskCount ,
        SUM(CASE WHEN cast(est.value as REAL) < cast(tot.value as REAL) THEN cast(tot.value as REAL) - cast(est.value as REAL)  ELSE 0 END) as OverTaskHour, 
        0 as odr
       FROM (SELECT * FROM ticket WHERE milestone = $MLS ), 
            (SELECT * FROM ticket_custom where name='estimatedhours') est,
            (SELECT * FROM ticket_custom where name='totalhours') tot
       WHERE id = est.ticket
         and id = tot.ticket
       GROUP by Hoge
UNION
       SELECT
        '合計' as Hoge,
        COUNT(*) as TaskCount ,
        SUM(cast(est.value as REAL)) as EstimateHour ,
        ROUND(SUM(cast(est.value as REAL)/$DH),2) as EstimateMD ,
        SUM(cast(tot.value as REAL)) as WorkHour ,
        ROUND(SUM(cast(tot.value as REAL)/$DH),2) as WorkMD ,
        SUM(CASE WHEN cast(est.value as REAL) < cast(tot.value as REAL) THEN 1 ELSE 0 END) as OverTaskCount ,
        SUM(CASE WHEN cast(est.value as REAL) < cast(tot.value as REAL) THEN cast(tot.value as REAL) - cast(est.value as REAL)  ELSE 0 END) as OverTaskHour, 
        0 as odr
       FROM (SELECT * FROM ticket WHERE milestone = $MLS ), 
            (SELECT * FROM ticket_custom where name='estimatedhours') est,
            (SELECT * FROM ticket_custom where name='totalhours') tot
       WHERE id = est.ticket
         and id = tot.ticket
UNION
       SELECT '未着手',0,0,0,0,0,0,0,0
UNION
       SELECT '作業中',0,0,0,0,0,0,0,0
UNION
       SELECT '完了' ,0,0,0,0,0,0,0,0
)
GROUP by Hoge
ORDER by _odr

プロジェクトメンバーの日別作業実績

0.11用
SELECT author as __group__ ,
       Day,
       分類,
       ticket,
       summary,
       作業時間,
       _ord as __color__ 
from (
select author||'('||strftime('%Y年%m月', 'now', 'localtime')||'作業分)'  as author,
        date(work_time.time, 'unixepoch', 'localtime') as Day ,
        (select t.type
            from ticket t where id = work_time.ticket) as 分類,
        ticket,
        (select  summary 
            from ticket where  id = work_time.ticket)
            as summary,
          sum(CAST(newvalue as REAL)) as 作業時間,
        0 as _ORD
 from ticket_change work_time
 where 
   work_time.field='hours' 
   and substr(date(work_time.time, 'unixepoch', 'localtime'),1,7) = strftime('%Y-%m', 'now')
   group by author,date(work_time.time, 'unixepoch','localtime') ,分類,Ticket

UNION

select author||'('||strftime('%Y年%m月', 'now', 'localtime')||'作業分)'  as author,
       ' ' as Day,
       ' ' as type,
       0   as ticket,
       '合計' as summary,
        sum(CAST(newvalue as REAL)) as 作業時間,
        99999 as _ORD
 from  ticket_change work_time
 where 
   work_time.field='hours' 
   and substr(date(work_time.time, 'unixepoch', 'localtime'),1,7) = strftime('%Y-%m', 'now')
   group by author
)
order by author,Day,_ord
0.12用

Trac本体の日時の持ち方がミリ秒単位に変わったため、日時を1000000分の1してます。

SELECT author as __group__ ,
       Day,
       分類,
       ticket,
       summary,
       作業時間,
       _ord as __color__ 
from (
select author||'('||strftime('%Y年%m月', '-1 month', 'localtime')||'作業分)'  as author,
        date(work_time.time/1000000, 'unixepoch', 'localtime') as Day ,
        (select t.type
            from ticket t where id = work_time.ticket) as 分類,
        ticket,
        (select  summary 
            from ticket where  id = work_time.ticket)
            as summary,
          sum(CAST(newvalue as REAL)) as 作業時間,
        0 as _ORD
 from ticket_change work_time
 where 
   work_time.field='hours' 
   and substr(date(work_time.time/1000000, 'unixepoch', 'localtime'),1,7) = strftime('%Y-%m', 'now')
   group by author,date(work_time.time/1000000, 'unixepoch','localtime') ,分類,Ticket

UNION

select author||'('||strftime('%Y年%m月', '-1month', 'localtime')||'作業分)'  as author,
       ' ' as Day,
       ' ' as type,
       0   as ticket,
       '合計' as summary,
        sum(CAST(newvalue as REAL)) as 作業時間,
        99999 as _ORD
 from  ticket_change work_time
 where 
   work_time.field='hours' 
   and substr(date(work_time.time/1000000, 'unixepoch', 'localtime'),1,7) = strftime('%Y-%m', 'now')
   group by author
)
order by author,Day,_ord

諸般の事情について

TracLightning3系の3.0.7以前に付属するReportIncludeプラグインAPI変更に伴って動いていません。
TracLightning3系の3.0.8以降にバージョンアップするか、Shibuya.tracのリポジトから
ReportIncludeプラグインを再ダウンロードしてインストールし直して下さい。

Shibuya.tracはデブサミ2011のオフィシャルコミュニティです #devsumi

2月17日(木),18日(金)の両日に開催されるhttp://codezine.jp/devsumi/2011:Title=Developers Summit 2011のオフィシャルコミュニティとして Shibuya.trac がブースとコミュニティLTでの発表枠を頂けることになりました。

セッション開催中はブースを不在にすることが少なくないと思いますが、休憩時間には居るようにしたいと思いますので是非お立ち寄りください。
Tracの使い方はもちろん、プラグインの使い方レポートやワークフローなどなど、皆さんの疑問質問にお答えできればと思ってます。
ちなみに予定は未定ですがお立ち寄りいただいた方には特典を用意するつもりでいます。

スタッフとして手伝いたいとかブース番しても良いよというかたはsf.jpのDevelopers Summit 2011のページにエントリお願いします。



デブサミ2011注目のセッション

2月17日 17:40-19:10【17-E-7】
デブサミオフィシャルコミュニティから選出のLT大会2011」

私がShibuya.tracとしてLTします。生暖かい応援*1を宜しくお願い致します。

2月17日 14:20-15:05【17-B-4】
「チケット管理システム大決戦 JIRA vs Redmine vs Trac 〜ユーザーが語る、なぜ私はこのツールを使うのか 」

アトラシアンの大澤さん(twitter:@Sean_SF])のパネルディスカッションにShibuya.tracでお馴染みの吉羽さん([twitter:@Ryuzee])がTracユーザ代表として登壇します。Redmineユーザの代表はTiDDのあきぴーさんが登壇するとのことで、Tracユーザに限らずRedmine/JIRAのユーザも注目のセッションだと思います。
http://www.ryuzee.com/contents/blog/3571

2月17日 15:25-16:15【17-B-5】
「今そこにあるScrum」

最近アジャイル関連の勉強会に出ることが多いのですが、そこで非常にお世話になっている永和システムマネジメントの西村さん([twitter:@nawoto])のセッション。アジャイルコーチとして自身の経験から、経営層にも注目されて来つつあるScrumの導入際に配慮すべきことについて話してくれるそうです。
デブサミ2011にて「今そこにあるScrum」という話をさせていただきます #devsumi - ヲトナ.backtrace


2月17日 13:10-14:00【17-B-3】
チケット駆動開発〜タスクマネジメントからAgile開発へ〜」

「Redmineによるタスクマネジメント実践技法」の著者である あきぴーさんこと 小川さんと、さかばさんこと 阪井さんによるセッション。TiDDに注目している人は必見のセッションだと思います。


さらに!!
デブサミの2日目の夜にShibuya.trac第10回勉強会

デブサミの2日目の夜(2/18)にShibuya.tracの勉強会が企画進行中!
今回は関西からの あきぴーさん、さかばさんの参加に加えて、中部からの刺客もやってくる予定になっています。
Shibuya.tracの名を冠しながらもRedmine臭漂うShibuya.trac第10回勉強会は要チェックですよ!
Shibuya.trac第10回勉強会の詳細はこちら

*1:横断幕とかウチワとかは不要w