[MERGE] [ADD] website_twitter: add twitter scroller snippet

bzr revid: odo@openerp.com-20140410174622-j2waid38nk3fqitt
This commit is contained in:
Barad Mahendra 2014-04-10 19:46:22 +02:00 committed by Olivier Dony
parent 7c75b5e004
commit 0152bea512
20 changed files with 774 additions and 0 deletions

View File

@ -0,0 +1,2 @@
import models
import controllers

View File

@ -0,0 +1,25 @@
{
'name': 'Twitter Roller',
'category': 'Website',
'summary': 'Add twitter scroller snippet in website builder',
'version': '1.0',
'description': """
Display best tweets
========================
""",
'author': 'OpenERP SA',
'depends': ['website'],
'data': [
'security/ir.model.access.csv',
'data/twitter_data.xml',
'views/twitter_view.xml',
'views/twitter_snippet.xml'
],
'demo': [],
'qweb': [],
'js': [],
'css': [],
'installable': True,
'application': True,
}

View File

@ -0,0 +1 @@
import main

View File

@ -0,0 +1,32 @@
from openerp.addons.web import http
from openerp.addons.web.http import request
from openerp.tools.translate import _
import json
class Twitter(http.Controller):
@http.route(['/twitter_reload'], type='json', auth="user", website=True)
def twitter_reload(self):
return request.website.fetch_favorite_tweets()
@http.route(['/get_favorites'], type='json', auth="public", website=True)
def get_tweets(self, limit=20):
key = request.website.twitter_api_key
secret = request.website.twitter_api_secret
screen_name = request.website.twitter_screen_name
if not key or not secret:
return {"error": _("Please set the Twitter API Key and Secret in the Website Settings.")}
if not screen_name:
return {"error": _("Please set a Twitter screen name to load favorites from, "
"in the Website Settings (it does not have to be yours)")}
twitter_tweets = request.registry['website.twitter.tweet']
tweets = twitter_tweets.search_read(
request.cr, request.uid,
[('website_id','=', request.website.id),
('screen_name','=', screen_name)],
['tweet'], limit=int(limit), order="tweet_id desc", context=request.context)
if len(tweets) < 12:
return {"error": _("Twitter user @%(username)s has less than 12 favorite tweets. "
"Please add more or choose a different screen name.") % \
{'username': screen_name}}
return [json.loads(tweet['tweet']) for tweet in tweets]

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="ir_cron_twitter_actions" model="ir.cron">
<field name="name">Fetch new Twitter favorites</field>
<field name="interval_number">2</field>
<field name="interval_type">hours</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
<field name="model">website</field>
<field name="function">_refresh_favorite_tweets</field>
<field name="args">()</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,3 @@
import twitter
import twitter_config

View File

@ -0,0 +1,115 @@
from urllib2 import urlopen, Request, HTTPError
import base64
import json
import logging
import werkzeug
from openerp.osv import fields, osv
API_ENDPOINT = 'https://api.twitter.com'
API_VERSION = '1.1'
REQUEST_TOKEN_URL = '%s/oauth2/token' % API_ENDPOINT
REQUEST_FAVORITE_LIST_URL = '%s/%s/favorites/list.json' % (API_ENDPOINT, API_VERSION)
URLOPEN_TIMEOUT = 10
_logger = logging.getLogger(__name__)
class TwitterClient(osv.osv):
_inherit = "website"
_columns = {
'twitter_api_key': fields.char('Twitter API key', help="Twitter API Key"),
'twitter_api_secret': fields.char('Twitter API secret', help="Twitter API Secret"),
'twitter_screen_name': fields.char('Get favorites from this screen name'),
}
def _request(self, website, url, params=None):
"""Send an authenticated request to the Twitter API."""
access_token = self._get_access_token(website)
if params:
params = werkzeug.url_encode(params)
url = url + '?' + params
try:
request = Request(url)
request.add_header('Authorization', 'Bearer %s' % access_token)
return json.load(urlopen(request, timeout=URLOPEN_TIMEOUT))
except HTTPError, e:
_logger.debug("Twitter API request failed with code: %r, msg: %r, content: %r",
e.code, e.msg, e.fp.read())
raise
def _refresh_favorite_tweets(self, cr, uid, context=None):
''' called by cron job '''
website = self.pool['website']
ids = self.pool['website'].search(cr, uid, [('twitter_api_key', '!=', False),
('twitter_api_secret', '!=', False),
('twitter_screen_name', '!=', False)],
context=context)
_logger.debug("Refreshing tweets for website IDs: %r", ids)
website.fetch_favorite_tweets(cr, uid, ids, context=context)
def fetch_favorite_tweets(self, cr, uid, ids, context=None):
website_tweets = self.pool['website.twitter.tweet']
tweet_ids = []
for website in self.browse(cr, uid, ids, context=context):
if not all((website.twitter_api_key, website.twitter_api_secret,
website.twitter_screen_name)):
_logger.debug("Skip fetching favorite tweets for unconfigured website %s",
website)
continue
params = {'screen_name': website.twitter_screen_name}
last_tweet = website_tweets.search_read(
cr, uid, [('website_id', '=', website.id),
('screen_name', '=', website.twitter_screen_name)],
['tweet_id'],
limit=1, order='tweet_id desc', context=context)
if last_tweet:
params['since_id'] = int(last_tweet[0]['tweet_id'])
_logger.debug("Fetching favorite tweets using params %r", params)
response = self._request(website, REQUEST_FAVORITE_LIST_URL, params=params)
for tweet_dict in response:
tweet_id = tweet_dict['id'] # unsigned 64-bit snowflake ID
tweet_ids = website_tweets.search(cr, uid, [('tweet_id', '=', tweet_id)])
if not tweet_ids:
new_tweet = website_tweets.create(
cr, uid,
{
'website_id': website.id,
'tweet': json.dumps(tweet_dict),
'tweet_id': tweet_id, # stored in NUMERIC PG field
'screen_name': website.twitter_screen_name,
},
context=context)
_logger.debug("Found new favorite: %r, %r", tweet_id, tweet_dict)
tweet_ids.append(new_tweet)
return tweet_ids
def _get_access_token(self, website):
"""Obtain a bearer token."""
bearer_token_cred = '%s:%s' % (website.twitter_api_key, website.twitter_api_secret)
encoded_cred = base64.b64encode(bearer_token_cred)
request = Request(REQUEST_TOKEN_URL)
request.add_header('Content-Type',
'application/x-www-form-urlencoded;charset=UTF-8')
request.add_header('Authorization',
'Basic %s' % encoded_cred)
request.add_data('grant_type=client_credentials')
data = json.load(urlopen(request, timeout=URLOPEN_TIMEOUT))
access_token = data['access_token']
return access_token
class WebsiteTwitterTweet(osv.osv):
_name = "website.twitter.tweet"
_description = "Twitter Tweets"
_columns = {
'website_id': fields.many2one('website', string="Website"),
'screen_name': fields.char("Screen Name"),
'tweet': fields.text('Tweets'),
# Twitter IDs are 64-bit unsigned ints, so we need to store them in
# unlimited precision NUMERIC columns, which can be done with a
# float field. Used digits=(0,0) to indicate unlimited.
# Using VARCHAR would work too but would have sorting problems.
'tweet_id': fields.float("Tweet ID", digits=(0,0)), # Twitter
}

View File

@ -0,0 +1,42 @@
import logging
from openerp.osv import fields, osv
from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
class twitter_config_settings(osv.osv_memory):
_inherit = 'website.config.settings'
_columns = {
'twitter_api_key': fields.related(
'website_id', 'twitter_api_key', type="char",
string='Twitter API Key',
help="Twitter API key you can get it from https://apps.twitter.com/app/new"),
'twitter_api_secret': fields.related(
'website_id', 'twitter_api_secret', type="char",
string='Twitter API secret',
help="Twitter API secret you can get it from https://apps.twitter.com/app/new"),
'twitter_tutorial': fields.dummy(
type="boolean", string="Show me how to obtain the Twitter API Key and Secret"),
'twitter_screen_name': fields.related(
'website_id', 'twitter_screen_name',
type="char", string='Get favorites from this screen name',
help="Screen Name of the Twitter Account from which you want to load favorites."
"It does not have to match the API Key/Secret."),
}
def _check_twitter_authorization(self, cr, uid, config_id, context=None):
website_obj = self.pool['website']
website_config = self.browse(cr, uid, config_id, context=context)
try:
website_obj.fetch_favorite_tweets(cr, uid, [website_config.website_id.id], context=context)
except Exception:
_logger.warning('Failed to verify twitter API authorization', exc_info=True)
raise osv.except_osv(_('Twitter authorization error!'), _('Please double-check your Twitter API Key and Secret'))
def create(self, cr, uid, vals, context=None):
res_id = super(twitter_config_settings, self).create(cr, uid, vals, context=context)
if vals.get('twitter_api_key') and vals.get('twitter_api_secret'):
self._check_twitter_authorization(cr, uid, res_id, context=context)
return res_id

View File

@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_website_twitter_tweet_public,access of twitter snippet,website_twitter.model_website_twitter_tweet,,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_website_twitter_tweet_public access of twitter snippet website_twitter.model_website_twitter_tweet 1 0 0 0

View File

@ -0,0 +1,3 @@
sass:
sass -t expanded --compass --unix-newlines --watch website.twitter.sass:website.twitter.css

View File

@ -0,0 +1,110 @@
.wrap-row {
position: relative;
overflow: hidden;
height: 310px;
}
.wrap-row .twitter-row {
position: absolute;
width: 100%;
height: auto;
}
.wrap-row .twitter-row div.scrollWrapper {
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
}
.wrap-row .twitter-row div.scrollableArea {
position: relative;
width: auto;
height: 100%;
}
.wrap-row .twitter-row div .tweet {
border: 1px solid #cccccc;
max-width: 500px;
width: 500px;
font-size: 0.8em;
padding-top: 12px;
padding-right: 10px;
padding-bottom: 12px;
padding-left: 10px;
float: left;
display: block;
margin: 6px;
max-height: 90px;
height: 90px;
opacity: 0.6;
}
.wrap-row .twitter-row div .tweet h4, .wrap-row .twitter-row div .tweet p {
padding: 0;
margin: 0;
}
.wrap-row .twitter-row div .tweet .left {
display: block;
float: left;
width: 80px;
}
.wrap-row .twitter-row div .tweet .left img {
width: 65px;
height: auto;
float: left;
display: block;
margin: 0px 5px 0px -5px;
}
.wrap-row .twitter-row div .tweet .right {
display: block;
float: left;
width: 470px;
}
.wrap-row .twitter-row div .tweet .right .top {
height: 20px;
}
.wrap-row .twitter-row div .tweet h4 {
font-size: 14px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: bold;
color: black;
float: left;
display: block;
position: relative;
margin-left: 70px;
margin-top: -65px;
}
.wrap-row .twitter-row div .tweet h4 span {
color: #cccccc;
font-weight: bold;
font-size: 14px;
}
.wrap-row .twitter-row div .tweet p {
line-height: 1.5em;
float: left;
position: relative;
display: block;
}
.wrap-row .twitter-row div .tweet .date {
float: right;
line-height: 0.5em;
margin-top: -60px;
margin-right: -10px;
}
.wrap-row .twitter-row div .tweet .right .bottom p {
margin-top: -65px;
margin-left: 70px;
font-size: 12px;
word-break: break-word;
}
.wrap-row .twitter-row div .tweet:hover {
-webkit-box-shadow: 0.5px 0.5px 0.5px 1px #428bca;
-moz-box-shadow: 0.5px 0.5px 0.5px 1px #428bca;
box-shadow: 0.5px 0.5px 0.5px 1px #428bca;
cursor: pointer;
opacity: 1;
}
@media screen and (max-width: 580px) {
.wrap-row {
position: relative;
overflow: hidden;
height: 100px;
}
}

View File

@ -0,0 +1,94 @@
.wrap-row
position: relative
overflow: hidden
height: 310px
.twitter-row
position: absolute
width: 100%
height: auto
div
&.scrollWrapper
position: relative
overflow: hidden
width: 100%
height: 100%
&.scrollableArea
position: relative
width: auto
height: 100%
.tweet
border: 1px solid #ccc
max-width: 500px
width: 500px
font-size: 0.8em
padding-top: 12px
padding-right: 10px
padding-bottom: 12px
padding-left: 10px
float: left
display: block
margin: 6px
max-height: 90px
height: 90px
opacity: 0.6
h4, p
padding: 0
margin: 0
.left
display: block
float: left
width: 80px
img
width: 65px
height: auto
float: left
display: block
margin: 0px 5px 0px -5px
.right
display: block
float: left
width: 470px
.top
height: 20px
h4
font-size: 14px
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif
font-weight: bold
color: #000
float: left
display: block
position: relative
margin-left: 70px
margin-top: -65px
span
color: #ccc
font-weight: bold
font-size: 14px
p
line-height: 1.5em
float: left
position: relative
display: block
&.date
float: right
line-height: 0.5em
margin-top: -60px
margin-right: -10px
.right .bottom p
margin-top: -65px
margin-left: 70px
font-size: 12px
word-break: break-word
&:hover
-webkit-box-shadow: 0.5px 0.5px 0.5px 1px #428BCA
-moz-box-shadow: 0.5px 0.5px 0.5px 1px #428BCA
box-shadow: 0.5px 0.5px 0.5px 1px #428BCA
cursor: pointer
opacity: 1
@media screen and (max-width: 580px)
.wrap-row
position: relative
overflow: hidden
height: 100px

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,144 @@
(function () {
'use strict';
var website = openerp.website,
qweb = openerp.qweb;
qweb.add_template('/website_twitter/static/src/xml/website.twitter.xml');
if (!website.snippet) website.snippet = {};
website.snippet.animationRegistry.twitter = website.snippet.Animation.extend({
selector: ".twitter",
start: function () {
var self = this;
var timeline = this.$target.find(".twitter_timeline");
this.$target.on('click', '.twitter_timeline .tweet', function($event) {
if ($event.target.tagName.toLowerCase() !== "a") {
var url = $($event.currentTarget).data('url');
if (url) {
window.open(url, '_blank');
}
else {
debugger;
}
}
});
$("<center><div><img src='/website_twitter/static/src/img/loadtweet.gif'></div></center>").appendTo(timeline);
openerp.jsonRpc('/get_favorites','call', {}).then(function(data) {
self.$target.find(".twitter_timeline").empty();
if (data.error) {
self.error(data);
}
else {
self.render(data);
self.setupMouseEvents();
}
});
},
stop: function() {
$(this).find('.scrollWrapper').each(function(index, el){
self.stop_scrolling($(el));
});
this.clearMouseEvents();
},
error: function(data){
var $error = $(qweb.render("website.Twitter.Error", {'data': data}));
$error.appendTo(this.$target.find(".twitter_timeline"));
},
parse_tweet: function (tweet) {
var create_link = function (url, text) {
var c = $("<a>", {
text: text,
href: url,
target: "_blank"
});
return c.prop("outerHTML");
};
return tweet.text.replace(/[A-Za-z]+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&~\?\/.=]+/g,
function (url) { return create_link(url, url) })
.replace(/[@]+[A-Za-z0-9_]+/g,
function (screen_name) { return create_link("http://twitter.com/" + screen_name.replace("@",""), screen_name); })
.replace(/[#]+[A-Za-z0-9_]+/g,
function (hashtag) { return create_link("http://twitter.com/search?q="+hashtag.replace("#",""), hashtag); });
},
parse_date: function(tweet) {
var d = new Date(tweet.created_at);
return d.toDateString();
},
setupMouseEvents: function() {
var self = this;
this.$scroller.mouseenter(function() {
$(this).find('.scrollWrapper').each(function(index, el){
self.stop_scrolling($(el));
});
}).mouseleave(function() {
$(this).find('.scrollWrapper').each(function(index, el){
self.start_scrolling($(el));
});
});
},
clearMouseEvents: function() {
if (this.$scroller) {
this.$scroller.off('mouseenter')
.off('mouseleave');
}
},
render: function(data){
var self = this;
var timeline = this.$target.find(".twitter_timeline");
var tweets = [];
$.each(data, function (e, tweet) {
tweet.created_at = self.parse_date(tweet);
tweet.text = self.parse_tweet(tweet);
tweets.push(qweb.render("website.Twitter.Tweet", {'tweet': tweet}));
});
var f = Math.floor(tweets.length / 3);
var tweet_slice = [tweets.slice(0, f).join(" "), tweets.slice(f, f * 2).join(" "), tweets.slice(f * 2, tweets.length).join(" ")];
this.$scroller = $(qweb.render("website.Twitter.Scroller"));
this.$scroller.appendTo(this.$target.find(".twitter_timeline"));
this.$scroller.find("div[id^='scroller']").each(function(index, element){
var scrollWrapper = $('<div class="scrollWrapper"></div>');
var scrollableArea = $('<div class="scrollableArea"></div>');
scrollWrapper.append(scrollableArea)
.data('scrollableArea', scrollableArea);
scrollableArea.append(tweet_slice[index]);
$(element).append(scrollWrapper);
scrollableArea.width(self.get_wrapper_width(scrollableArea));
scrollWrapper.scrollLeft(index*180);
self.start_scrolling(scrollWrapper);
});
},
get_wrapper_width: function(wrapper){
var total_width = 0;
wrapper.children().each(function(){
total_width += $(this).outerWidth(true);
});
return total_width;
},
start_scrolling: function(wrapper){
var self = this;
wrapper.data("getNextElementWidth", true);
wrapper.data("autoScrollingInterval", setInterval(function () {
wrapper.scrollLeft(wrapper.scrollLeft() + 1);
self.swap_right(wrapper);
}, 20));
},
stop_scrolling: function(wrapper){
clearInterval(wrapper.data('autoScrollingInterval'));
},
swap_right: function(wrapper){
if (wrapper.data("getNextElementWidth")) {
wrapper.data("swapAt", wrapper.data("scrollableArea").children(":first").outerWidth(true));
wrapper.data("getNextElementWidth", false);
}
if (wrapper.data("swapAt") <= wrapper.scrollLeft()){
var swap_el = wrapper.data("scrollableArea").children(":first").detach();
wrapper.data("scrollableArea").append(swap_el);
wrapper.scrollLeft(wrapper.scrollLeft() - swap_el.outerWidth(true));
wrapper.data("getNextElementWidth", true);
}
},
});
})();

View File

@ -0,0 +1,51 @@
(function () {
'use strict';
var website = openerp.website,
qweb = openerp.qweb;
website.snippet.options["twitter"] = website.snippet.options.marginAndResize.extend({
start: function(){
this._super();
this.make_hover_config();
this.$target.find('.lnk_configure').click(function(e){
window.location = e.target.href;
});
if (this.$target.data("snippet-view")) {
this.$target.data("snippet-view").stop();
}
},
twitter_reload: function(){
openerp.jsonRpc('/twitter_reload','call', {});
},
make_hover_config: function(){
var self = this;
var $configuration = $(qweb.render("website.Twitter.Reload")).hide().appendTo(document.body).click(function (e) {
e.preventDefault();
e.stopPropagation();
self.twitter_reload();
});
this.$target.on('mouseover', '', function () {
var $selected = $(this);
var position = $selected.offset();
$configuration.show().offset({
top: $selected.outerHeight() / 2
+ position.top
- $configuration.outerHeight() / 2,
left: $selected.outerWidth() / 2
+ position.left
- $configuration.outerWidth() / 2,
})
}).on('mouseleave', '', function (e) {
var current = document.elementFromPoint(e.clientX, e.clientY);
if (current === $configuration[0]) {
return;
}
$configuration.hide();
});
},
clean_for_save: function () {
this.$target.find(".twitter_timeline").empty();
},
});
})();

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="website.Twitter.Tweet">
<div class="tweet" t-attf-data-url="http://twitter.com/#{tweet.user.screen_name}/status/#{tweet.id_str}" t-attf-data-tweet-id="#{tweet.id_str}">
<div class="left">
<img t-att-src="tweet.user.profile_image_url"/>
</div>
<div class="right">
<div class="top">
<h4>
<t t-esc="tweet.user.name"/>
<span>
<a t-att-href="'https://twitter.com/' + tweet.user.screen_name" target="_blank"><t t-esc="'@' + tweet.user.screen_name "/></a>
</span>
</h4>
<a class="date" target="_blank" t-attf-href="http://twitter.com/#{tweet.user.screen_name}/status/#{tweet.id_str}"><t t-esc="tweet.created_at"/></a>
</div>
<div class="bottom"><p><t t-raw="tweet.text"/></p></div>
</div>
</div>
</t>
<t t-name="website.Twitter.Scroller">
<div class="wrap-row" contenteditable="false">
<div class="twitter-row">
<div class="twitter-scroller">
<div id="scroller1" ></div>
<div id="scroller2" ></div>
<div id="scroller3" ></div>
</div>
</div>
</div>
</t>
<t t-name="website.Twitter.Reload">
<button type="button" contenteditable="false" class="btn btn-primary hover-edition-button">Reload</button>
</t>
<t t-name="website.Twitter.Error">
<div class="alert alert-info" contenteditable="false">
<t t-esc="data.error"/>
<i t-if='!data.nodata' class="fa fa-plus-circle">
<a class="lnk_configure" href="/web#action=website.action_website_configuration">Twitter Configuration</a>
</i>
</div>
</t>
</templates>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="website_twitter_snippet" name="Twitter snippet" inherit_id="website.snippets">
<xpath expr="//div[@id='snippet_feature']" position="inside">
<div>
<div class="oe_snippet_thumbnail">
<img class="oe_snippet_thumbnail_img" src="/website_twitter/static/src/img/twitter_scroll.png"/>
<span class="oe_snippet_thumbnail_title">Twitter Scroller</span>
</div>
<section class="oe_snippet_body twitter" data-screen-name="OpenERP" data-limit="15">
<div class="twitter_timeline" contenteditable="false"></div>
</section>
</div>
</xpath>
</template>
<template id="website_twitter_options" name="Twitter Options" inherit_id="website.snippet_options">
<xpath expr="//div" position="after">
<div data-snippet-option-id='twitter'
data-selector=".twitter"
data-selector-children=".oe_structure, [data-oe-type=html]">
</div>
</xpath>
</template>
<template id="twitter" inherit_id="website.layout" name="Twitter Snippet">
<xpath expr="//head" position="inside">
<link rel="stylesheet" href="/website_twitter/static/src/css/website.twitter.css" type="text/css"/>
<script type="text/javascript" src="/website_twitter/static/src/js/website.twitter.animation.js"></script>
<script type="text/javascript" src="/website_twitter/static/src/js/website.twitter.editor.js" groups="base.group_website_publisher"></script>
</xpath>
</template>
</data>
</openerp>

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_website_config_settings" model="ir.ui.view">
<field name="name">Twitter settings</field>
<field name="model">website.config.settings</field>
<field name="inherit_id" ref="website.view_website_config_settings"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='social_twitter']" position="after">
<label for="id" string="Twitter API"/>
<div>
<p>Set your Twitter API access below to be able to use the Twitter Scroller Website snippet.<br/>
You can get your API credentials from <a href="https://apps.twitter.com/app/new" target="new">https://apps.twitter.com/app/new</a>
</p>
<div>
<field name="twitter_tutorial" class="oe_inline"/><label for="twitter_tutorial"/>
<blockquote class="small" attrs="{'invisible':[('twitter_tutorial','=',False)]}">
<h2>How to configure the Twitter API access</h2>
<ol>
<li>Create a new Twitter application on <a href="https://apps.twitter.com/app/new" target="new">https://apps.twitter.com/app/new</a>
with the following values:
<ul>
<li><strong>Name: </strong> OpenERP Tweet Scroller</li>
<li><strong>Description: </strong> OpenERP Tweet Scroller </li>
<li><strong>Website: </strong> <tt>http://www.openerp.com</tt></li>
<li><strong>Callback URL: </strong> leave it blank</li>
<li>Accept terms of use and click on the Create button at the bottom</li>
</ul>
</li>
<li>Switch to the API Keys tab: <br/>
<img src='/website_twitter/static/src/img/api_key.png'/>
</li>
<li>Copy/Paste API Key and Secret below</li>
<li>Enter the screen name from which you want to load favorite Tweets (does not need to be the same as the API keys)</li>
</ol>
</blockquote>
</div>
<div>
<label for="twitter_api_key"/>
<field name="twitter_api_key" class="oe_inline"/>
</div>
<div>
<label for="twitter_api_secret"/>
<field name="twitter_api_secret" class="oe_inline"/>
</div>
<div>
<label for="twitter_screen_name"/>
<field name="twitter_screen_name" class="oe_inline"/>
</div>
</div>
</xpath>
</field>
</record>
</data>
</openerp>