内容


构建一个 Twitter Web 应用程序

用 Django 和 jQuery 编写 Web 2.0 风格的应用程序

Comments

最近,Twitter 似乎无所不在。政客、演员、父母 — 只要您说得出来 — 都在使用社交网络媒介。客户不仅要求 Web 应用程序是支持 Twitter 的,而且还要求它具备出色的 Web 2.0 外观,而这只能通过 Ajax 获得。让我们进入 Django 和 jQuery 的世界。

图 1. Twitter 面板显示了最新的 tweet
屏幕截图:Twitter 面板显示了最新的 tweet
屏幕截图:Twitter 面板显示了最新的 tweet

依赖项

我强烈建议您通读 Django 教程,然后再开始动手。此外,还要求您具备扎实的 JavaScript 基础知识。当然,Twitter 帐户以及对术语的了解(比如什么是 tweet)也必不可少。而您的工具箱中则应该包含 Python、Django、jQuery 和 python-twitter 包装程序。相关链接,请参考 参考资料

本文中的这个 Web 应用程序不需要数据库;Twitter 基础结构将保存所有数据。但是,作为对此应用程序的一个改进,也可以集成一个数据库,甚至可以使用 Django 和数据库来将您的 Twitter 帐户上的内容复制到您自己的服务器上以生成副本。本文还假设您使用的是 Linux®。

我使用 Python V2.5.2 测试这个示例应用程序。Python 在大多数 Linux 机器上是默认安装好的;如果您的机器上没有安装 Python,那么关于下载和安装该语言的信息,可以参见 参考资料

Twitter 的数据通过两个 API 公开给公众,一个是搜索 API,一个是 RESTful API。(Twitter FAQ 中提到过将来计划只用单一一个 API)。一个 REST Web 服务指的是在 HTTP 内实现的一个 Web 服务,它遵循 REST 原理(更多信息,请参阅 参考资料)。

安装 python-twitter 包装程序

我们所需的最后一个组件是 python-twitter,其项目站点上将它定义为 “围绕 Twitter API 及 twitter 数据模型的一个 python 包装程序”。现在已经有几个库可以通过多种语言与 Twitter 的服务交互 — 从 Ruby 到 Eiffel,几乎任何语言。目前,有五个库可以交互 Python 与 Twitter,它们是 DeWitt Clinton 的 Python-twitter、Andrew Price 的 python-twyt、Dustin Sallings 的 twitty-twister、Ryan McGrath 的 twython 以及 Josh Roesslein 的 Tweepy。

python-twitter 库需要依赖项 simplejson(参见 参考资料)。在下载 simplejson 后,通过发出清单 1 内的这些命令来安装它。

清单 1. 安装 simplejson 所需的命令
tar -zxvf simplejson-2.0.9.tar.gz
cd simplejson-2.0.9
sudo python setup.py build
sudo python setup.py install

如果您更愿意使用 egg 包,可以使用命令 sudo easy_install simplejson-2.0.9-py2.5-win32.egg

现在,可以安装 python-twitter。在下载了这个包之后,执行清单 2 内的命令。

清单 2. 安装 python-twitter 所需的命令
tar -zxvf python-twitter-0.6.tar.gz
cd python-twitter-0.6
sudo python setup.py build
sudo python setup.py install

安装 Django

接下来要安装的是 Django,它是一种功能强大的 Python Web 框架。本文中的示例应用程序是用 V1.0.2 final 编写的,但最新的稳定版本已经是 1.1。 Django 的安装与 simplejson 和 python-twitter 的安装一样简单。在下载了这个包后,在终端内输入清单 3 内的命令即可。

清单 3. 安装 Django 所需的命令
tar -zxvf Django-1.0.2.tar.gz
cd Django-1.0.2
sudo python setup.py build
sudo python setup.py install

通过尝试使用命令 $ django-admin --version 来验证 Django 是否处于您的路径内。如果一切进展顺利,就可以开始编写代码了。

python-twitter 库

在安装了 python-twitter 之后,就可以探索它的功能了。要访问这个库,可以使用命令 import twittertwitter 模块为 Twitter 数据模型和 API 提供了包装程序。这里有三个数据模型类:twitter.Statustwitter.Usertwitter.DirectMessage。所有的 API 调用都会返回其中一个类的一个对象。要访问此 API,需要生成 twitter.Api()(注意其中的第一个字母是大写的)的一个实例。在不为这个构造函数提供任何实参的情况下创建实例是允许的,但只限于不需要登录的那些方法调用。要进行登录访问,请使用如下代码:

import twitter
api = twitter.Api(username='yourUserName', password='yourPassword')

借助一个实例化了的 Api 对象,您就获得自己的追随者、您所追随的人和状态 (tweet);以及发布您当前的状态等。例如,要获得一个用户的状态列表,可以调用 statuses = api.GetUserTimeline(),它会返回 twitter.Status 对象的一个列表。要创建具有这些 tweet 的文本(字符串)的另外一个列表,应该使用列表理解(list comprehension):>>>print [s.text for s in statuses]

表 1 列出了在这个 Web 应用程序内将要用到的类和方法。要想获得完整的文档,请使用 pydocs — 比如,$ pydoc twitter.Api— 或访问 python-twitter 文档页面(参见 参考资料)。

表 1. 一些有用的 python-twitter API 方法
方法名称返回描述
GetUserTimeline()twitter.Status 对象的列表获得一个用户的所有 tweet
GetFriends()twitter.User 对象的列表获得追随者列表
PostUpdate()单一一个 twitter.Status发布一个 tweet

加入 jQuery

准确来讲,我们所使用的不仅仅是 jQuery 内核,还有 jQuery UI,它提供了小部件、主题和动画。转向 ThemeRoller 页面,在左侧的面板中,单击 Gallery 选项卡。选择自己喜欢的一个主题(我为这个示例应用程序使用了 Cupertino),然后单击主题名下面的 Download。这么做会链接到实际的下载页面;选择当前(稳定)版本,在作者写作本文之时,jQuery V1.3+ 的版本是 1.7.2。

这里没有安装过程;只需解压缩 ThemeRoller 文件并在正确的地方放置正确的目录/文件。如果愿意,还可以自己创建主题,但为了简便起见,我使用的是预先已有的一个主题。

创建 Django 项目

接下来,需要创建一个新的 Django 项目。使用命令 django-admin startproject pytweetproj,其中 pytweetproj 是本文中为此项目使用的名称。也可以使用别的名称,但一定要在项目中自始至终使用该名称。更改到 pytweetproj 目录。为了启动 Django Web 服务器,在命令行键入 python manage.py runserver,打开一个 Web 浏览器,并导航到 http://127.0.0.1:8000。应该会得到一个页面,提示说 “It worked!”。如果在任何时候想要停止或重启这个服务器,可以按 Ctrl+C 来终止这个过程。不要将开发服务器用于生产。在完成了项目的开发之后,应该转移到一个安全的生产服务器,比如 Apache。

在 root 项目目录,创建一个名为 resources 的子目录,内含 CSS 文件、JavaScript 文件等。从解压之前下载的 ThemeRoller 文件的地方,将 css 和 js 目录(解压缩自 ThemeRoller ZIP 文件)以及 jQuery 内核移动或复制到 resources 目录。

利用自己喜欢的文本编辑器,打开 settings.py 文件并改变它来匹配清单 4 内所示的设置。

清单 4. Django 设置
# Django settings for pytweetproj project.
import os
APPLICATION_DIR = os.path.dirname( globals()[ '__file__' ] )

DEBUG = True
TEMPLATE_DEBUG = DEBUG

ADMINS = (
)

MANAGERS = ADMINS

# Not important to change this, we're not using any databases
DATABASE_ENGINE = ''           
DATABASE_NAME = ''             
DATABASE_USER = ''             
DATABASE_PASSWORD = ''         
DATABASE_HOST = ''             
DATABASE_PORT = ''             

TIME_ZONE = 'America/Chicago'
LANGUAGE_CODE = 'en-us'

SITE_ID = 1

# if you won't be using internationalization, better to keep
# it false for speed
USE_I18N = False

MEDIA_ROOT = os.path.join( APPLICATION_DIR, 'resources' )
MEDIA_URL = 'http://localhost:8000/resources/'
ADMIN_MEDIA_PREFIX = '/media/'

# Make this unique, and don't share it with anybody.
SECRET_KEY = '=y^moj$+yfgwy2kc7^oexnl-f6(b#rkvvhq6c-ckks9_c#$35'

# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
    'django.template.loaders.filesystem.load_template_source',
    'django.template.loaders.app_directories.load_template_source',
#     'django.template.loaders.eggs.load_template_source',
)

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
)

ROOT_URLCONF = 'pytweetproj.urls'

TEMPLATE_DIRS = (
     os.path.join( APPLICATION_DIR, 'templates' ), 
)

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'twitterPanel',
)

# added for sessions
SESSION_ENGINE = 'django.contrib.sessions.backends.file'

若要以独立于操作系统的方式获得当前的工作目录,可以使用 os.path.dirname( globals()[ '__file__' ] ) 设置应用程序目录。可以使用相同的方法设置 resources 目录的路径,以及 TEMPLATE_DIR。此外,还需要将 MEDIA_URL 设置为 http://localhost:8000/resources/。

由于我们不使用数据库,所以无需设置任何相关的变量,但仍需设置 ROOT_URLCONF。注意到在 INSTALLED_APPS 内,有一个名为 twitterPanel 的值还未创建。我们接下来将要创建该 Django 应用程序。

要创建这个 twitterPanel 应用程序,可以使用 django-admin startapp twitterPanel 并键入 cd 以切换到最新创建的模块目录:twitterPanel。在这个 twitterPanel 目录,创建另一个目录,名为 templates,内含 Django 模板。

打造 URL

用自己喜欢的文本编辑器,打开项目根内的文件 url.py 并参照清单 5 更改代码。这个文件将 URL 映射到回调函数。

清单 5. Root URL 设置
from django.conf.urls.defaults import *
from django.conf import settings

urlpatterns = patterns('',
    ( r'^resources/(?P<path>.*)$', 
      'django.views.static.serve', 
      { 'document_root': settings.MEDIA_ROOT } ),
    ( r'^twitterPanel/', include( 'twitterPanel.urls' ) ),
)

urlpatterns 内的第一个映射为使用静态服务的资源或媒介(例如,图像文件)定义了一种匹配。由于我们使用 Django 开发服务器来服务静态内容,因而使用了 django.views.static.serve() 视图。第二个映射包括了 twitterPanel 应用程序内的那些 URL 模式。打开 twitterPanel 下的 url.py 文件(不要将该 url.py 与根下的那个文件混淆)并添加清单 6 内的代码。

清单 6. Twitter 面板 URL
from django.conf.urls.defaults import *
from twitterPanel.views import *

urlpatterns = patterns( '',
    url( r'^$', index, name = 'twitterPanel_index' ),
    url( r'^panel/$', panel, name = 'twitterPanel_panel' ),
    url( r'^update/$', update, name = 'twitterPanel_update' ),
)

视图

我们将只创建三个视图:index、panel 和 update。注意到我们无需向这个根项目添加视图 — 只需向 Twitter Panel 应用程序添加视图。打开 twitterPanel 目录下的 view.py 文件并在文件的顶部添加清单 7 所示的导入。

清单 7. Twitter 面板视图导入
from django.shortcuts import render_to_response
from django.template.context import RequestContext
import twitter

render_to_response() 方法是一个 Django 快捷方式,用来呈现一个具有特定上下文的模板,它返回的是一个 HttpResponse 对象。RequestContext 是一个 Django 上下文,与常规的 django.template.Context 有些许差异。尤其是,它接受一个 HttpRequest 对象,并会用 TEMPLATE_CONTEXT_PROCESSORS 设置内的变量自动填充上下文。

第一个视图方法 index() 应该如清单 8 所示。

清单 8. index 视图
def index(request):
    template = 'index.html'
    userName = request.POST.get( 'userTxt' )
    password = request.POST.get( 'passwordTxt' )

    if userName is not None and password is not None:        
        try:
            api = twitter.Api( username = userName, password = password) 
            # just grab the first 5 tweets
            statuses = api.GetUserTimeline()[0:5] 
            # limit to just the first 120
            following = api.GetFriends()[0:120] 
        except NameError, e:
            print "unable to login"

    else:
        statuses = {}
        following = False 

    # place values in session
    request.session['statuses'] = statuses
    request.session['following'] = following
    request.session['userName'] = userName
    request.session['password'] = password
    
    data = {
        'statuses' : statuses,
        'following' : following,
    }

    return render_to_response(template, data, 
           context_instance = RequestContext(request))

模板 index.html 还尚未编写。此时,您只需记住目前有两个 HTML 元素:一个是用来输入用户名的文本框,一个是密码输入框。数据通过 POST 方法发送给此视图。如果用户名和密码均已输入,就会实例化一个 twitter.Api 对象并检索这个已登录用户的前 5 个 tweet 以及前 120 个您追随的人。状态、朋友、用户名以及密码均存储于这个会话内以便日后供程序的其他部分检索。

第二个视图方法 panel() 则更为简短,如清单 9 所示。

清单 9. panel 视图
def panel(request):
    template = 'panel.html'
    data = {
        'statuses' : request.session['statuses'],
        'following' : request.session['following']
    }
    return render_to_response(template, data,
           context_instance = RequestContext(request))

这里,我们使用的是 panel.html 模板,该模板同样也尚未存在。panel 是您登录进来后,所有动作发生的地方 — 换言之,就是显示 tweet、进行更新以及显示朋友的地方。稍后,我们还会详加讨论。目前,我们所要做的是从此会话获得一些数据,然后进行一次 render_and_response。相比之下,第三个视图方法 update 多少有些复杂。参见清单 10.

清单 10. update 视图
def update(request):
    template = 'panel.html'
  
    userName = request.session['userName']
    password = request.session['password']
 
    try:
        api = twitter.Api( username = request.session['userName'], 
                           password = request.session['password']) 
        api.PostUpdate(request.POST.get('updateTextArea'))
        updated = True
        statuses = api.GetUserTimeline()[0:5] # reload the statuses
    except NameError, e:
        print "unable to login"
        updated = False

    data = {
        'userName' : request.session['userName'],
        'password' : request.session['password'],
        'updated'  : updated,
        'statuses' : statuses
    }
    return render_to_response(template, data,
           context_instance = RequestContext(request))

用户名和密码取自这个会话,被用来实例化另外一个 twitter.Api 对象,然后使用 api 对象来向此帐户发布一个更新。这些状态之后会被重新加载。

模板

在 twitterPanel/templates 内,创建一个名为 index.html 的文件并打开它。用清单 11 内的代码填充这个文件。

清单 11. 索引模板
<html>
  <head>
    <title>Sample Python-Twitter Application</title>
    <link type="text/css" 
        href="{{MEDIA_URL}}css/cupertino/jquery-ui-1.7.2.custom.css" 
        rel="stylesheet" />
    <script type="text/javascript" 
        src="{{MEDIA_URL}}js/jquery-1.3.2.min.js"></script>
    <script type="text/javascript" 
        src="{{MEDIA_URL}}js/jquery-ui-1.7.2.custom.min.js"></script>
    <script type="text/javascript">

      $(function(){
        // Dialog
        $('#dialog').dialog({
          autoOpen: false,
          width: 400,
          buttons: {
            "Ok": function() {
              $(this).dialog("close");

              $.post('{% url twitterPanel_index %}', $("#loginForm").serialize()); 
              $( '#panel' ).html( '&nbsp;' ).load( '{% url twitterPanel_panel %}' )

              $( '#dialog_link' ).fadeOut(1500);
              $( '#demoTitle' ).fadeOut(1500);

            },
            "Cancel": function() {
              $(this).dialog("close");
            }
          }
        });

        // Dialog Link
        $('#dialog_link').click(function(){
          $('#dialog').dialog('open');
          return false;
        });
        //hover states on the static widgets
        $('#dialog_link, ul#icons li').hover(
          function() { $(this).addClass('ui-state-hover'); },
          function() { $(this).removeClass('ui-state-hover'); }
        );
      });
    </script>
    <style type="text/css">
      body{ font: 62.5% "Trebuchet MS", sans-serif; margin: 50px;}
      .demoHeaders { margin-top: 2em; }
      #dialog_link {padding: .4em 1em .4em 20px; 
         text-decoration:none; position: relative;}
      #dialog_link span.ui-icon {
         margin: 0 5px 0 0;position: absolute; 
         left: .2em;top: 50%;margin-top: -8px;}
      ul#icons {margin: 0; padding: 0;}
      ul#icons li {
         margin: 2px; position: relative; padding: 4px 0; 
         cursor: pointer; float: left;  list-style: none;}
      ul#icons span.ui-icon {float: left; margin: 0 4px;}
    </style>
  </head>

  <body>

  <h2 id="demoTitle" class="demoHeaders">PyTwitter Demo</h2>
  <p>
    <a href="#" id="dialog_link" class="ui-state-default ui-corner-all">
      <span class="ui-icon ui-icon-newwin"></span>Start Demo
    </a>
  </p>

  <div style="position: relative; width: 96%; height: 200px; padding:1% 4%; 
       overflow:hidden;" class="fakewindowcontain">
  </div>


  <!-- ui-dialog -->
  <div id="dialog" title="Login" >
    <p>
      <form action="#" name="loginForm" id="loginForm" method="POST">
        <table>
          <tr>
            <td>user</td> <td><input type="text" name="userTxt" id="userTxt"/></td>
          </tr>
          <tr>
            <td>password</td> 
            <td><input type="password" name="passwordTxt" id="passwordTxt"/></td>
          </tr>
        </table>
      </form>
    </p>
  </div>

  <!-- only show if logged in -->
  <!-- Accordion -->
  <div id="panel" style="position: absolute; top: 2%; left:2%; 
     width:600px; visibility:visible;"> 
  </div>
                
  </body>
</html>

图 2 显示了此应用程序的开始部分:一个 jQuery UI 按钮。

图 2. jQuery UI 按钮
显示 jQuery UI 按钮的屏幕截图

这个按钮调用 Login 窗口,如图 3 所示。

图 3. Login 窗口
图 3 显示了 login 窗口
图 3 显示了 login 窗口

让我们对 清单 11 内的代码进行分解讨论。首先,利用 {{MEDIA_URL}} 告诉浏览器在哪里可以找到 CSS、JavaScript 和 jQuery 文件。在 Django 呈现此模板时,它会用实际的路径代替 {{MEDIA_URL}}

在 header 内,添加自己编写的 JavaScript。美元符号($)是 jQuery 实例的别名。document.getElementById('name') 的 jQuery 快捷方式是 $('#name')。比如,用 <div> 标记可以定义一个 jQuery UI 元素 dialog,然后再调用 .dialog() 方法来创建 jQuery 窗口。在此窗口内,添加两个按钮:OKCancelCancel 的作用是关闭窗口,而 OK 则执行两个动作:先关闭窗口,然后再发布到服务器。如果 index 视图接收到的数据是正确的凭证,那么就可以得到 tweet 和追随者数据。 然后,窗口按钮和标题淡出,Twitter 面板逐渐淡入。

这里最有魔力的一行代码是 $( '#panel' ).html( '&nbsp;' ).load( '{% url twitterPanel_panel %}' ),它加载外部的 Django 模板 panel.html。Twitter 面板是一个 jQuery UI accordion 小部件。清单 12 给出了此面板模板的相应代码。将代码保存到 twitterPanel/templates 内的 panel.html。

清单 12. Twitter 面板模板
  <script type="text/javascript">
    $(function(){
      $("#main").accordion({
        header: "h3",
        autoHeight: true
      })
      .hide()
      .fadeIn(1500);

      $('#updateTextArea').keyup(function(){ 
        // text used for non-input elements. Var() is used for input elements
        var tweetLength =  $('#updateTextArea').val().length;
        $('#charCount').text( 140 - tweetLength );
      });

      $('#updateBtn').click(function(){
        if($('#updateTextArea').val().length <= 140){
          // submit 
          $.post('{% url twitterPanel_update %}', $("#updateForm").serialize()); 
          $("#updateStatus").text("last update: " + $('#updateTextArea').val());
          // clear text area
          $('#updateTextArea').val(""); 
          // reset counter too
          $('#charCount').text( "140" );
        } 
        return false;
      });
    });
  </script>                        
  
  <div id="main">
    <div>
      <h3><a href="#">My Recent Tweets</a></h3>
      <div>
        {% if statuses %}
          {% for s in statuses %}
            <p style="border-top:1px dashed #f00; margin-top:1em; 
                padding-top:1em font: 80% 'Trebuchet MS', sans-serif;">
              {{ s.text }}
            </p>
          {% endfor %}
        {% else %}
          No tweets found
        {% endif %}
      </div>
    </div>
    <div>
      <h3><a href="#">Update Status</a></h3>
      <div>
        <form action="#" id="updateForm" name="updateForm" method="POST">
          <textarea id="updateTextArea" name="updateTextArea" rows=2 cols=70 />
          <button id="updateBtn" class="ui-state-default ui-corner-all">Update</button>
        </form>
        <div id="charCount" name="charCount" value=140>140</div>
        <div id="updateStatus" name="updateStatus"><!-- update message here --></div>
      </div>
    </div>
    <div>
      <h3><a href="#">Following</a></h3>
      <div>
        {% if following %}
          {% for f in following %}
            <img src="{{ f.GetProfileImageUrl }}" width=28 height=28 />
          {% endfor %}
        {% else %}
          following variable is empty
        {% endif %}
      </div>
    </div>
  </div>

图 4 显示了具有 Update Status 选项卡的这个 Twitter 面板。

图 4. 显示 Update Status 选项卡的 Twitter 面板
图 4 显示了具有 Update Status 选项卡的 Twitter 面板的屏幕截图
图 4 显示了具有 Update Status 选项卡的 Twitter 面板的屏幕截图

将这个 accordion 小部件命名为 “main”,这个小部件是您登录进来后所有动作发生的地方。在 accordion 内,有三个选项卡:My Recent Tweets、Update Status 和 Following,如图 5 所示。

图 5. 显示 Following 选项卡的 Twitter 面板
图 5 显示了具有 Following 选项卡的 Twitter 面板的屏幕截图
图 5 显示了具有 Following 选项卡的 Twitter 面板的屏幕截图

第一个选项卡只显示前 5 个 tweet。如果 status 对象不为空,Django 就会循环遍历每个 status 对象并显示相应的文本。否则,则会在此选项卡内显示一个 “No tweets found” 消息。

第二个选项卡 Update Status,包含一个具有文本区域和按钮的 HTML 表单。在此表单下是一个 <div>,名为 charCount,之后是另外一个 <div>,名为 updateStatus。Update Status 还会有一个 onclick 事件;消息长度会被测试以确保其不超过 140 个字符的限制,如果不超过,就利用 jQuery post 方法向 update 视图发送更新文本,由它负责其余的事情。这样一来,最新的 tweet 就显示在此页面内了。

为了增加趣味性,我还添加了一些 JavaScript,用来计算这个 tweet 所剩的字符数。请记住,Twitter 具有 140 个字符/tweet 的限制。其工作原理是这样的: 当松开一个键时,update text 框内的字符数就被从 140 减掉。字符计数再被相应地在这个页面上更新。

Twitter 面板内的第三个选项卡是 Following,它先是检查 following 对象是否为 null。如果是,就像 My Recent Tweets 选项卡那样,循环遍历每个用户对象。每个用户都具有一个概要文件图像 URL:将该值放入 <image> 标记的 src 属性。结果,您所 “追随” 的每个用户都会显示在第三个选项卡内。

现在,万事俱备之后,就可以启动 Django 服务器并访问 http://127.0.0.1:8000/twitterPanel。您应该会看到一个用于登录的 jQuery UI 按钮。单击此按钮后,会出现 Login 窗口。键入您自己的 twitter 凭证,Twitter 面板就会出现。

结束语

现在,您看过了这个演示后,可能已经注意到了 jQuery UI 为应用程序的动态感觉增加了很多效果。Django 还简化了 Python 的后端和模板处理。

这个应用程序还可以进一步改进,比如向登录过程添加适当的验证、添加错误窗口或您的追随者的列表;发送直接的 tweet;以及搜索 tweet。当然,您也可以自由发挥自己的创造力。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source
ArticleID=460472
ArticleTitle=构建一个 Twitter Web 应用程序
publish-date=01072010