PK Im$! $ django-la-facebook-latest/.buildinfo# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config:
tags:
PK Ir r % django-la-facebook-latest/objects.inv# Sphinx inventory version 2
# Project: django-la-facebook
# Version: 0.1
# The remainder of this file is compressed using zlib.
xڥJ1QE >@4l.l(뎧BHo"<0LBܳ-wi]~9beP&HDZ_f #hb<48h^|QWOHuyw#Wj<OB(p%m!
jKi([q&Eeeǯʈdr9{^j2yr|hYO[V]"]TM|%0'PK ItH%~ %~ $ django-la-facebook-latest/index.html
In order to authenticate your site’s users with Facebook, you need a unique
identifier for Facebook to associate your site with. Facebook considers your
site an “app” and so you must acquire an FACEBOOK_APP_ID and
FACEBOOK_APP_SECRET from the
Facebook Developer app.
You will need to enter your sites domain into the Facebook developer app, and
the site will restrict authentication requests to that domain. For initial
testing and experiments, you will want to enter “http://localhost/” for the
website URL. For later testing, you will probably want create a staging or
testing subdomain. The domain set here must match the domain entered into
Django’s sites framework.
See the documentation for Settings about how to enter these values in your
Django settings file.
provides a method of creating a django.contrib.auth user model for an
authenticated facebook user
Updates fields in the model pointed to by the AUTH_PROFILE_MODULE
setting that match available fields in Facebook’s user data.
Creates and manages an association object that stores the user’s associated
facebook id, authentication token (which is used to access the information
in the users facebook profile as authorized by the user), and the expiration
date of that token.
These steps are handled by directing a user to the la_facebook login view. By
default this view, like Django’s login url LOGIN_URL, will use a next
querystring parameter to redirect the browser to a page after user
authenticates with Facebook.
If an error occurs during authentication, or the user denies to authenticate,
the browser is redirected to a template located at
la_facebook/fb_error.html (see the provided template for some information
about what context variables may be provided in the case of an error).
If you wish a more customized behavior for Facebook authentication, see the
Callbacks documentation.
In your login page, or as part of your login form, you should include a link to
the Facebook login view, which will then redirect the user to facebook to login
and authenticate to your site. A simple example might look like this:
<p><a href="{% url la_facebook_login %}?next={{ next }}">Login with FaceBook</a></p>
You can used the following image provided by facebook as a graphical link:
By default the login flow redirects you to facebook for login in your main
browser window, and the layout of this page is suited to be a complete page.
Facebook also supports an alternate “popup” display style, which is better
suited for popup windows. This can reduce the feeling that the user is leaving
your site. You must pass the display:popup option to the login view in your
own project urls.py, and you must handle the creation and destruction of the
actual popup window yourself.
This document describes the settings needed by la_facebook.
FACEBOOK_ACCESS_SETTINGS this is the only setting required. It is a dictionary
of app specific settings as follows:
FACEBOOK_APP_ID - this is a key that uniquely identifies your Facebook app, in
the common case, the Facebook app will be your project or site.
FACEBOOK_APP_SECRET - This secret is used in generating the authenticated
token. Both the key and the secret are available through the Facebook’s Developer app.
CALLBACK (optional) - this is a dotted module path string (similar to using a string for
a view) that points to a subclass of la_facebook.callbacks.default. The default
value is “la_facebook.callbacks.default.default_facebook_callback”
PROVIDER_SCOPE (optional) - a list, of strings, of permissions to ask for.
The list of these is here
LOG_LEVEL (optional) - A string value containing one of standard python logging
levels of DEBUG, INFO, WARNING, ERROR or CRITICAL. Defaults to “ERROR”, which
should be relatively quiet.
LOG_FILE (optional) - The path to a file that will received appended logging
information. By default, logged messages will print to stdout.
Example:
FACEBOOK_ACCESS_SETTINGS={"FACEBOOK_APP_ID":FACEBOOK_APP_ID,"FACEBOOK_APP_SECRET":FACEBOOK_APP_SECRET,# The following keys are optional# "CALLBACK": "la_facebook.callbacks.default.default_facebook_callback",# "PROVIDER_SCOPE": ['email','read_stream'],# "LOG_LEVEL": "DEBUG",# "LOG_FILE": "/tmp/la_facebook.log",}
In the context of la_facebook, callbacks are a subclass of
la_facebook.callbacks.base.BaseFacebookCallback.
The callback is called after the user authenticates with Facebook, either for
the first time, or when returning after authentication expiration. The callback
determines what happens next, whether that be a user being created, or the
browser being redirected.
la_facebook ships with two callbacks. BaseFacebookCallback defines the key
steps that any response to a facebook auth should include, but is largely an
abstract class. DefaultFacebookCallback is a working callback that addresses
the most common use case for basic user auth. Most projects will
probably be fine to just use this default implementation.
When a new user comes to your site, and clicks on a link that calls the
la_facebook.views.facebook_login view, they are redirected to facebook
where they are authenticated and approve your app. They are then redirected
to your site and the default callback is executed. This callback fist checks
whether there is already an authenticated Django user in the session. If not
the Facebook id of the user is examined and it is determined whether they have
previously authenticated to the site by looking for database model that stores
an association between a Django user and a Facebook user.
If there exists a prior association, the expiration of that users Facebook
authentication token is updated, the Django session expiration is set to match,
and the user is logged and redirected.
If no prior association is present, a django user and its association object
are created. The default Django username consists of the facebook user’s name
sluggified and concatenated with their facebook id. Any profile object, as
defined in Django’s settings, is updated with any matching fields in the users
Facebook data.
is called by the view and handles the basic check of whether the user
is authenticated and dispatches to the other methods as nessasary and then
returns the response to the view and thus to the browser (usually a redirect)
Given a valid user, the user is first logged in, and then their Facebook
data is created or updated through persist. Finally the session’s
expiration is set to match the expiration of the Facebook auth token.
This documents provides a quick overview of the events involved in
authenticating a user against facebook.
Facebook has had various authentication methods in the past (Facebook Connect),
but has currently standardized on using OAuth 2 Protocol.
Facebook’s own documentation does a reasonable job of
explaining the process, but it is summarized here. Facebook offers two
workflows for user authentication, client (javascript) based, and server side.
This project aims to provide a Python based, server side option.
There are three parts to Facebook’s authentication:
user authentication (ensures that the user is who they say they are)
app authorization (ensures that the user knows exactly what data and capabilities they are providing to your site)
app authentication (ensures that the user is giving their information to your app and not someone else)
Facebook will only authenticate a user in relation to a specific app, there is
no “just authorize the user” option. In our case, the “app” that is
authenticated is your entire Django project or site, not a specific Django app.
For Facebook to associate your site with the authentication, you will need an
app ID from Facebook’s Developer app,
which is placed in your Django settings file.
A user is authenticated to facebook, and your app in one step. The first time
this happens, the user will be prompted to approve the level of access you are
asking for. By default and at a minimum this includes the basic info that is
publicly available about the user on Facebook. (for additional permissions, see
the documentation for settings). The permissions approved will be global to all
your Django apps in your Django project.
Once your site is authorized by Facebook, an authorization token is stored in
a model associated with a Django user (which by default is created if needed).
That user that will then be the authenticated user for subsequent requests
(request.user).
Security is HARD.
Security is DANGEROUS.
Doing authentication via a third-party JavaScript library is STUPID.
Yet authentication via Facebook’s JavaScript library is all over the place.
The better way would be to do authentication via Facebook-flavored OAUTH on the backend. With a well documented, testable project complete with working code examples. The working code examples should be in a dirt simple test project. The test project allows a developer to quickly analyze why facebook auth is failing without the complications of working in their entire system stack.
Our goals:
Good documentation that will build on readthedocs.org.
A lot of the core code of this project was liberally lifted from django-oauth-access. We want to extend all gratitude and thanks to the guys who made that excellent project.
')
.appendTo($('#searchbox'));
}
},
/**
* init the domain index toggle buttons
*/
initIndexTable : function() {
var togglers = $('img.toggler').click(function() {
var src = $(this).attr('src');
var idnum = $(this).attr('id').substr(7);
$('tr.cg-' + idnum).toggle();
if (src.substr(-9) == 'minus.png')
$(this).attr('src', src.substr(0, src.length-9) + 'plus.png');
else
$(this).attr('src', src.substr(0, src.length-8) + 'minus.png');
}).css('display', '');
if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) {
togglers.click();
}
},
/**
* helper function to hide the search marks again
*/
hideSearchWords : function() {
$('#searchbox .highlight-link').fadeOut(300);
$('span.highlighted').removeClass('highlighted');
},
/**
* make the url absolute
*/
makeURL : function(relativeURL) {
return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL;
},
/**
* get the current relative url
*/
getCurrentURL : function() {
var path = document.location.pathname;
var parts = path.split(/\//);
$.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() {
if (this == '..')
parts.pop();
});
var url = parts.join('/');
return path.substring(url.lastIndexOf('/') + 1, path.length - 1);
}
};
// quick alias for translations
_ = Documentation.gettext;
$(document).ready(function() {
Documentation.init();
});
PK vI8c c / django-la-facebook-latest/_static/websupport.js/*
* websupport.js
* ~~~~~~~~~~~~~
*
* sphinx.websupport utilties for all documentation.
*
* :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
(function($) {
$.fn.autogrow = function() {
return this.each(function() {
var textarea = this;
$.fn.autogrow.resize(textarea);
$(textarea)
.focus(function() {
textarea.interval = setInterval(function() {
$.fn.autogrow.resize(textarea);
}, 500);
})
.blur(function() {
clearInterval(textarea.interval);
});
});
};
$.fn.autogrow.resize = function(textarea) {
var lineHeight = parseInt($(textarea).css('line-height'), 10);
var lines = textarea.value.split('\n');
var columns = textarea.cols;
var lineCount = 0;
$.each(lines, function() {
lineCount += Math.ceil(this.length / columns) || 1;
});
var height = lineHeight * (lineCount + 1);
$(textarea).css('height', height);
};
})(jQuery);
(function($) {
var comp, by;
function init() {
initEvents();
initComparator();
}
function initEvents() {
$(document).on("click", 'a.comment-close', function(event) {
event.preventDefault();
hide($(this).attr('id').substring(2));
});
$(document).on("click", 'a.vote', function(event) {
event.preventDefault();
handleVote($(this));
});
$(document).on("click", 'a.reply', function(event) {
event.preventDefault();
openReply($(this).attr('id').substring(2));
});
$(document).on("click", 'a.close-reply', function(event) {
event.preventDefault();
closeReply($(this).attr('id').substring(2));
});
$(document).on("click", 'a.sort-option', function(event) {
event.preventDefault();
handleReSort($(this));
});
$(document).on("click", 'a.show-proposal', function(event) {
event.preventDefault();
showProposal($(this).attr('id').substring(2));
});
$(document).on("click", 'a.hide-proposal', function(event) {
event.preventDefault();
hideProposal($(this).attr('id').substring(2));
});
$(document).on("click", 'a.show-propose-change', function(event) {
event.preventDefault();
showProposeChange($(this).attr('id').substring(2));
});
$(document).on("click", 'a.hide-propose-change', function(event) {
event.preventDefault();
hideProposeChange($(this).attr('id').substring(2));
});
$(document).on("click", 'a.accept-comment', function(event) {
event.preventDefault();
acceptComment($(this).attr('id').substring(2));
});
$(document).on("click", 'a.delete-comment', function(event) {
event.preventDefault();
deleteComment($(this).attr('id').substring(2));
});
$(document).on("click", 'a.comment-markup', function(event) {
event.preventDefault();
toggleCommentMarkupBox($(this).attr('id').substring(2));
});
}
/**
* Set comp, which is a comparator function used for sorting and
* inserting comments into the list.
*/
function setComparator() {
// If the first three letters are "asc", sort in ascending order
// and remove the prefix.
if (by.substring(0,3) == 'asc') {
var i = by.substring(3);
comp = function(a, b) { return a[i] - b[i]; };
} else {
// Otherwise sort in descending order.
comp = function(a, b) { return b[by] - a[by]; };
}
// Reset link styles and format the selected sort option.
$('a.sel').attr('href', '#').removeClass('sel');
$('a.by' + by).removeAttr('href').addClass('sel');
}
/**
* Create a comp function. If the user has preferences stored in
* the sortBy cookie, use those, otherwise use the default.
*/
function initComparator() {
by = 'rating'; // Default to sort by rating.
// If the sortBy cookie is set, use that instead.
if (document.cookie.length > 0) {
var start = document.cookie.indexOf('sortBy=');
if (start != -1) {
start = start + 7;
var end = document.cookie.indexOf(";", start);
if (end == -1) {
end = document.cookie.length;
by = unescape(document.cookie.substring(start, end));
}
}
}
setComparator();
}
/**
* Show a comment div.
*/
function show(id) {
$('#ao' + id).hide();
$('#ah' + id).show();
var context = $.extend({id: id}, opts);
var popup = $(renderTemplate(popupTemplate, context)).hide();
popup.find('textarea[name="proposal"]').hide();
popup.find('a.by' + by).addClass('sel');
var form = popup.find('#cf' + id);
form.submit(function(event) {
event.preventDefault();
addComment(form);
});
$('#s' + id).after(popup);
popup.slideDown('fast', function() {
getComments(id);
});
}
/**
* Hide a comment div.
*/
function hide(id) {
$('#ah' + id).hide();
$('#ao' + id).show();
var div = $('#sc' + id);
div.slideUp('fast', function() {
div.remove();
});
}
/**
* Perform an ajax request to get comments for a node
* and insert the comments into the comments tree.
*/
function getComments(id) {
$.ajax({
type: 'GET',
url: opts.getCommentsURL,
data: {node: id},
success: function(data, textStatus, request) {
var ul = $('#cl' + id);
var speed = 100;
$('#cf' + id)
.find('textarea[name="proposal"]')
.data('source', data.source);
if (data.comments.length === 0) {
ul.html('
No comments yet.
');
ul.data('empty', true);
} else {
// If there are comments, sort them and put them in the list.
var comments = sortComments(data.comments);
speed = data.comments.length * 100;
appendComments(comments, ul);
ul.data('empty', false);
}
$('#cn' + id).slideUp(speed + 200);
ul.slideDown(speed);
},
error: function(request, textStatus, error) {
showError('Oops, there was a problem retrieving the comments.');
},
dataType: 'json'
});
}
/**
* Add a comment via ajax and insert the comment into the comment tree.
*/
function addComment(form) {
var node_id = form.find('input[name="node"]').val();
var parent_id = form.find('input[name="parent"]').val();
var text = form.find('textarea[name="comment"]').val();
var proposal = form.find('textarea[name="proposal"]').val();
if (text == '') {
showError('Please enter a comment.');
return;
}
// Disable the form that is being submitted.
form.find('textarea,input').attr('disabled', 'disabled');
// Send the comment to the server.
$.ajax({
type: "POST",
url: opts.addCommentURL,
dataType: 'json',
data: {
node: node_id,
parent: parent_id,
text: text,
proposal: proposal
},
success: function(data, textStatus, error) {
// Reset the form.
if (node_id) {
hideProposeChange(node_id);
}
form.find('textarea')
.val('')
.add(form.find('input'))
.removeAttr('disabled');
var ul = $('#cl' + (node_id || parent_id));
if (ul.data('empty')) {
$(ul).empty();
ul.data('empty', false);
}
insertComment(data.comment);
var ao = $('#ao' + node_id);
ao.find('img').attr({'src': opts.commentBrightImage});
if (node_id) {
// if this was a "root" comment, remove the commenting box
// (the user can get it back by reopening the comment popup)
$('#ca' + node_id).slideUp();
}
},
error: function(request, textStatus, error) {
form.find('textarea,input').removeAttr('disabled');
showError('Oops, there was a problem adding the comment.');
}
});
}
/**
* Recursively append comments to the main comment list and children
* lists, creating the comment tree.
*/
function appendComments(comments, ul) {
$.each(comments, function() {
var div = createCommentDiv(this);
ul.append($(document.createElement('li')).html(div));
appendComments(this.children, div.find('ul.comment-children'));
// To avoid stagnating data, don't store the comments children in data.
this.children = null;
div.data('comment', this);
});
}
/**
* After adding a new comment, it must be inserted in the correct
* location in the comment tree.
*/
function insertComment(comment) {
var div = createCommentDiv(comment);
// To avoid stagnating data, don't store the comments children in data.
comment.children = null;
div.data('comment', comment);
var ul = $('#cl' + (comment.node || comment.parent));
var siblings = getChildren(ul);
var li = $(document.createElement('li'));
li.hide();
// Determine where in the parents children list to insert this comment.
for(i=0; i < siblings.length; i++) {
if (comp(comment, siblings[i]) <= 0) {
$('#cd' + siblings[i].id)
.parent()
.before(li.html(div));
li.slideDown('fast');
return;
}
}
// If we get here, this comment rates lower than all the others,
// or it is the only comment in the list.
ul.append(li.html(div));
li.slideDown('fast');
}
function acceptComment(id) {
$.ajax({
type: 'POST',
url: opts.acceptCommentURL,
data: {id: id},
success: function(data, textStatus, request) {
$('#cm' + id).fadeOut('fast');
$('#cd' + id).removeClass('moderate');
},
error: function(request, textStatus, error) {
showError('Oops, there was a problem accepting the comment.');
}
});
}
function deleteComment(id) {
$.ajax({
type: 'POST',
url: opts.deleteCommentURL,
data: {id: id},
success: function(data, textStatus, request) {
var div = $('#cd' + id);
if (data == 'delete') {
// Moderator mode: remove the comment and all children immediately
div.slideUp('fast', function() {
div.remove();
});
return;
}
// User mode: only mark the comment as deleted
div
.find('span.user-id:first')
.text('[deleted]').end()
.find('div.comment-text:first')
.text('[deleted]').end()
.find('#cm' + id + ', #dc' + id + ', #ac' + id + ', #rc' + id +
', #sp' + id + ', #hp' + id + ', #cr' + id + ', #rl' + id)
.remove();
var comment = div.data('comment');
comment.username = '[deleted]';
comment.text = '[deleted]';
div.data('comment', comment);
},
error: function(request, textStatus, error) {
showError('Oops, there was a problem deleting the comment.');
}
});
}
function showProposal(id) {
$('#sp' + id).hide();
$('#hp' + id).show();
$('#pr' + id).slideDown('fast');
}
function hideProposal(id) {
$('#hp' + id).hide();
$('#sp' + id).show();
$('#pr' + id).slideUp('fast');
}
function showProposeChange(id) {
$('#pc' + id).hide();
$('#hc' + id).show();
var textarea = $('#pt' + id);
textarea.val(textarea.data('source'));
$.fn.autogrow.resize(textarea[0]);
textarea.slideDown('fast');
}
function hideProposeChange(id) {
$('#hc' + id).hide();
$('#pc' + id).show();
var textarea = $('#pt' + id);
textarea.val('').removeAttr('disabled');
textarea.slideUp('fast');
}
function toggleCommentMarkupBox(id) {
$('#mb' + id).toggle();
}
/** Handle when the user clicks on a sort by link. */
function handleReSort(link) {
var classes = link.attr('class').split(/\s+/);
for (var i=0; iThank you! Your comment will show up '
+ 'once it is has been approved by a moderator.');
}
// Prettify the comment rating.
comment.pretty_rating = comment.rating + ' point' +
(comment.rating == 1 ? '' : 's');
// Make a class (for displaying not yet moderated comments differently)
comment.css_class = comment.displayed ? '' : ' moderate';
// Create a div for this comment.
var context = $.extend({}, opts, comment);
var div = $(renderTemplate(commentTemplate, context));
// If the user has voted on this comment, highlight the correct arrow.
if (comment.vote) {
var direction = (comment.vote == 1) ? 'u' : 'd';
div.find('#' + direction + 'v' + comment.id).hide();
div.find('#' + direction + 'u' + comment.id).show();
}
if (opts.moderator || comment.text != '[deleted]') {
div.find('a.reply').show();
if (comment.proposal_diff)
div.find('#sp' + comment.id).show();
if (opts.moderator && !comment.displayed)
div.find('#cm' + comment.id).show();
if (opts.moderator || (opts.username == comment.username))
div.find('#dc' + comment.id).show();
}
return div;
}
/**
* A simple template renderer. Placeholders such as <%id%> are replaced
* by context['id'] with items being escaped. Placeholders such as <#id#>
* are not escaped.
*/
function renderTemplate(template, context) {
var esc = $(document.createElement('div'));
function handle(ph, escape) {
var cur = context;
$.each(ph.split('.'), function() {
cur = cur[this];
});
return escape ? esc.text(cur || "").html() : cur;
}
return template.replace(/<([%#])([\w\.]*)\1>/g, function() {
return handle(arguments[2], arguments[1] == '%' ? true : false);
});
}
/** Flash an error message briefly. */
function showError(message) {
$(document.createElement('div')).attr({'class': 'popup-error'})
.append($(document.createElement('div'))
.attr({'class': 'error-message'}).text(message))
.appendTo('body')
.fadeIn("slow")
.delay(2000)
.fadeOut("slow");
}
/** Add a link the user uses to open the comments popup. */
$.fn.comment = function() {
return this.each(function() {
var id = $(this).attr('id').substring(1);
var count = COMMENT_METADATA[id];
var title = count + ' comment' + (count == 1 ? '' : 's');
var image = count > 0 ? opts.commentBrightImage : opts.commentImage;
var addcls = count == 0 ? ' nocomment' : '';
$(this)
.append(
$(document.createElement('a')).attr({
href: '#',
'class': 'sphinx-comment-open' + addcls,
id: 'ao' + id
})
.append($(document.createElement('img')).attr({
src: image,
alt: 'comment',
title: title
}))
.click(function(event) {
event.preventDefault();
show($(this).attr('id').substring(2));
})
)
.append(
$(document.createElement('a')).attr({
href: '#',
'class': 'sphinx-comment-close hidden',
id: 'ah' + id
})
.append($(document.createElement('img')).attr({
src: opts.closeCommentImage,
alt: 'close',
title: 'close'
}))
.click(function(event) {
event.preventDefault();
hide($(this).attr('id').substring(2));
})
);
});
};
var opts = {
processVoteURL: '/_process_vote',
addCommentURL: '/_add_comment',
getCommentsURL: '/_get_comments',
acceptCommentURL: '/_accept_comment',
deleteCommentURL: '/_delete_comment',
commentImage: '/static/_static/comment.png',
closeCommentImage: '/static/_static/comment-close.png',
loadingImage: '/static/_static/ajax-loader.gif',
commentBrightImage: '/static/_static/comment-bright.png',
upArrow: '/static/_static/up.png',
downArrow: '/static/_static/down.png',
upArrowPressed: '/static/_static/up-pressed.png',
downArrowPressed: '/static/_static/down-pressed.png',
voting: false,
moderator: false
};
if (typeof COMMENT_OPTIONS != "undefined") {
opts = jQuery.extend(opts, COMMENT_OPTIONS);
}
var popupTemplate = '\
\ Sort by:\ best rated\ newest\ oldest\
\\
Add a comment\ (markup):
\``code``
, \ code blocks:::
and an indented block after blank line