Merge remote-tracking branch 'origin/master' into is315

This commit is contained in:
cfdv 2011-06-20 12:50:44 -05:00
commit 78c0744077
15 changed files with 160 additions and 34 deletions

View File

@ -108,8 +108,8 @@ Contributing changes
-------------------- --------------------
Slartibartfast from the planet Magrathea far off in the universe has Slartibartfast from the planet Magrathea far off in the universe has
decided that he is bored with fjords and wants to fix issue 42 and decided that he is bored with fjords and wants to fix issue 42 (the
send us the changes. meaning of life bug) and send us the changes.
Slartibartfast has cloned the MediaGoblin repository and his clone Slartibartfast has cloned the MediaGoblin repository and his clone
lives on gitorious. lives on gitorious.
@ -125,18 +125,18 @@ Slartibartfast does the following:
git fetch --all -p git fetch --all -p
2. Creates a branch from the tip of the MediaGoblin repository (the 2. Creates a branch from the tip of the MediaGoblin repository (the
remote is named ``gmg``) master branch called ``issue_42``:: remote is named ``gmg``) master branch called ``bug42_meaning_of_life``::
git checkout -b issue_42 gmg/master git checkout -b bug42_meaning_of_life gmg/master
3. Slartibartfast works hard on his changes in the ``issue_42`` 3. Slartibartfast works hard on his changes in the ``bug42_meaning_of_life``
branch. When done, he wants to notify us that he has made changes branch. When done, he wants to notify us that he has made changes
he wants us to see. he wants us to see.
4. Slartibartfast pushes his changes to his clone (the remote is named 4. Slartibartfast pushes his changes to his clone (the remote is named
``origin``):: ``origin``)::
git push origin issue_42 --set-upstream git push origin bug42_meaning_of_life --set-upstream
5. Slartibartfast adds a comment to issue 42 with the url for his 5. Slartibartfast adds a comment to issue 42 with the url for his
repository and the name of the branch he put the code in. He also repository and the name of the branch he put the code in. He also
@ -155,19 +155,19 @@ He runs the unit tests and discovers there's a bug in the code!
Then he does this: Then he does this:
1. He checks out the ``issue_42`` branch:: 1. He checks out the ``bug42_meaning_of_life`` branch::
git checkout issue_42 git checkout bug42_meaning_of_life
2. He fixes the bug and checks it into the ``issue_42`` branch. 2. He fixes the bug and checks it into the ``bug42_meaning_of_life`` branch.
3. He pushes his changes to his clone (the remote is named ``origin``):: 3. He pushes his changes to his clone (the remote is named ``origin``)::
git push origin issue_42 git push origin bug42_meaning_of_life
4. He adds another comment to issue 42 explaining about the mistake 4. He adds another comment to issue 42 explaining about the mistake
and how he fixed it and that he's pushed the new change to the and how he fixed it and that he's pushed the new change to the
``issue_42`` branch of his publicly available clone. ``bug42_meaning_of_life`` branch of his publicly available clone.
What happens next What happens next
@ -180,7 +180,7 @@ request with his changes and explains what they are.
Later, someone checks out his code and finds a problem with it. He Later, someone checks out his code and finds a problem with it. He
adds a comment to the issue tracker specifying the problem and asks adds a comment to the issue tracker specifying the problem and asks
Slartibartfast to fix it. Slartibartfst goes through the above steps Slartibartfast to fix it. Slartibartfst goes through the above steps
again, fixes the issue, pushes it to his ``issue_42`` branch and adds again, fixes the issue, pushes it to his ``bug42_meaning_of_life`` branch and adds
another comment to the issue tracker about how he fixed it. another comment to the issue tracker about how he fixed it.
Later, someone checks out his code and is happy with it. Someone Later, someone checks out his code and is happy with it. Someone
@ -192,8 +192,8 @@ Slartibartfast is notified of this. Slartibartfast does a::
git fetch --all git fetch --all
The changes show up in the ``master`` branch of the ``gmg`` remote. The changes show up in the ``master`` branch of the ``gmg`` remote.
Slartibartfast now deletes his ``issue_42`` branch because he doesn't Slartibartfast now deletes his ``bug42_meaning_of_life`` branch
need it anymore. because he doesn't need it anymore.
How to learn git How to learn git

View File

@ -136,7 +136,7 @@ This is fine in development, but if you want to actually run celery
separately for testing (or deployment purposes), you'll want to run separately for testing (or deployment purposes), you'll want to run
the server independently:: the server independently::
./bin/paster serve server.ini --reload ./bin/paster serve paste.ini --reload
Running celeryd Running celeryd
@ -158,7 +158,7 @@ Running the test suite
Run:: Run::
./bin/nosetests ./runtests.sh
Running a shell Running a shell

View File

@ -27,4 +27,4 @@ else
exit 1 exit 1
fi fi
CELERY_ALWAYS_EAGER=true $PASTER serve server.ini --reload CELERY_ALWAYS_EAGER=true $PASTER serve paste.ini --reload

View File

@ -14,6 +14,8 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from mediagoblin.util import cleaned_markdown_conversion
from mongokit import DocumentMigration from mongokit import DocumentMigration
@ -33,5 +35,19 @@ class MediaEntryMigration(DocumentMigration):
self.collection.update( self.collection.update(
self.target, self.update, multi=True, safe=True) self.target, self.update, multi=True, safe=True)
def allmigration02_add_description_html(self):
"""
Now that we can have rich descriptions via Markdown, we should
update all existing entries to record the rich description versions.
"""
self.target = {'description_html': {'$exists': False}}
if not self.status:
for doc in self.collection.find(self.target):
self.update = {
'$set': {
'description_html': cleaned_markdown_conversion(
doc['description'])}}
MIGRATE_CLASSES = ['MediaEntry'] MIGRATE_CLASSES = ['MediaEntry']

View File

@ -75,7 +75,8 @@ class MediaEntry(Document):
'title': unicode, 'title': unicode,
'slug': unicode, 'slug': unicode,
'created': datetime.datetime, 'created': datetime.datetime,
'description': unicode, 'description': unicode, # May contain markdown/up
'description_html': unicode, # May contain plaintext, or HTML
'media_type': unicode, 'media_type': unicode,
'media_data': dict, # extra data relevant to this media_type 'media_data': dict, # extra data relevant to this media_type
'plugin_data': dict, # plugins can dump stuff here. 'plugin_data': dict, # plugins can dump stuff here.

View File

@ -17,11 +17,13 @@
from webob import exc from webob import exc
from mediagoblin.util import render_to_response, redirect from mediagoblin.util import render_to_response, redirect, clean_html
from mediagoblin.edit import forms from mediagoblin.edit import forms
from mediagoblin.edit.lib import may_edit_media from mediagoblin.edit.lib import may_edit_media
from mediagoblin.decorators import require_active_login, get_user_media_entry from mediagoblin.decorators import require_active_login, get_user_media_entry
import markdown
@get_user_media_entry @get_user_media_entry
@require_active_login @require_active_login
@ -47,7 +49,14 @@ def edit_media(request, media):
u'An entry with that slug already exists for this user.') u'An entry with that slug already exists for this user.')
else: else:
media['title'] = request.POST['title'] media['title'] = request.POST['title']
media['description'] = request.POST['description'] media['description'] = request.POST.get('description')
md = markdown.Markdown(
safe_mode = 'escape')
media['description_html'] = clean_html(
md.convert(
media['description']))
media['slug'] = request.POST['slug'] media['slug'] = request.POST['slug']
media.save() media.save()

View File

@ -19,7 +19,8 @@ from cgi import FieldStorage
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from mediagoblin.util import render_to_response, redirect from mediagoblin.util import (
render_to_response, redirect, cleaned_markdown_conversion)
from mediagoblin.decorators import require_active_login from mediagoblin.decorators import require_active_login
from mediagoblin.submit import forms as submit_forms, security from mediagoblin.submit import forms as submit_forms, security
from mediagoblin.process_media import process_media_initial from mediagoblin.process_media import process_media_initial
@ -46,8 +47,14 @@ def submit_start(request):
# create entry and save in database # create entry and save in database
entry = request.db.MediaEntry() entry = request.db.MediaEntry()
entry['title'] = request.POST['title'] or unicode(splitext(filename)[0]) entry['title'] = (
request.POST['title']
or unicode(splitext(filename)[0]))
entry['description'] = request.POST.get('description') entry['description'] = request.POST.get('description')
entry['description_html'] = cleaned_markdown_conversion(
entry['description'])
entry['media_type'] = u'image' # heh entry['media_type'] = u'image' # heh
entry['uploader'] = request.user['_id'] entry['uploader'] = request.user['_id']

View File

@ -25,7 +25,9 @@
</h1> </h1>
<img class="media_image" src="{{ request.app.public_store.file_url( <img class="media_image" src="{{ request.app.public_store.file_url(
media.media_files.main) }}" /> media.media_files.main) }}" />
<p>{{ media.description }}</p> {% autoescape False %}
<p>{{ media.description_html }}</p>
{% endautoescape %}
<p>Uploaded on <p>Uploaded on
{{ "%4d-%02d-%02d"|format(media.created.year, {{ "%4d-%02d-%02d"|format(media.created.year,
media.created.month, media.created.day) }} media.created.month, media.created.day) }}

View File

@ -242,17 +242,69 @@ def test_authentication_views(test_app):
test_user.save() test_user.save()
# Get login # Get login
# ---------
test_app.get('/auth/login/') test_app.get('/auth/login/')
# Make sure it rendered with the appropriate template
assert util.TEMPLATE_TEST_CONTEXT.has_key( assert util.TEMPLATE_TEST_CONTEXT.has_key(
'mediagoblin/auth/login.html') 'mediagoblin/auth/login.html')
# Log in as that user # Failed login - blank form
# -------------------------
util.clear_test_template_context()
response = test_app.post('/auth/login/')
context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
form = context['login_form']
assert form.username.errors == [u'This field is required.']
assert form.password.errors == [u'This field is required.']
# Failed login - blank user
# -------------------------
util.clear_test_template_context()
response = test_app.post(
'/auth/login/', {
'password': u'toast'})
context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
form = context['login_form']
assert form.username.errors == [u'This field is required.']
# Failed login - blank password
# -----------------------------
util.clear_test_template_context()
response = test_app.post(
'/auth/login/', {
'username': u'chris'})
context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
form = context['login_form']
assert form.password.errors == [u'This field is required.']
# Failed login - bad user
# -----------------------
util.clear_test_template_context()
response = test_app.post(
'/auth/login/', {
'username': u'steve',
'password': 'toast'})
context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
assert context['login_failed']
# Failed login - bad password
# ---------------------------
util.clear_test_template_context()
response = test_app.post(
'/auth/login/', {
'username': u'chris',
'password': 'jam'})
context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
assert context['login_failed']
# Successful login
# ----------------
util.clear_test_template_context() util.clear_test_template_context()
response = test_app.post( response = test_app.post(
'/auth/login/', { '/auth/login/', {
'username': u'chris', 'username': u'chris',
'password': 'toast'}) 'password': 'toast'})
# User should be redirected
response.follow() response.follow()
assert_equal( assert_equal(
urlparse.urlsplit(response.location)[2], urlparse.urlsplit(response.location)[2],
@ -260,10 +312,38 @@ def test_authentication_views(test_app):
assert util.TEMPLATE_TEST_CONTEXT.has_key( assert util.TEMPLATE_TEST_CONTEXT.has_key(
'mediagoblin/root.html') 'mediagoblin/root.html')
# Make sure we're in the session or something # Make sure user is in the session
session = util.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']['request'].session context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
session = context['request'].session
assert session['user_id'] == unicode(test_user['_id']) assert session['user_id'] == unicode(test_user['_id'])
# Log out as that user # Successful logout
# Make sure we're not in the session # -----------------
util.clear_test_template_context()
response = test_app.get('/auth/logout/')
# Should be redirected to index page
response.follow()
assert_equal(
urlparse.urlsplit(response.location)[2],
'/')
assert util.TEMPLATE_TEST_CONTEXT.has_key(
'mediagoblin/root.html')
# Make sure the user is not in the session
context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
session = context['request'].session
assert session.has_key('user_id') == False
# User is redirected to custom URL if POST['next'] is set
# -------------------------------------------------------
util.clear_test_template_context()
response = test_app.post(
'/auth/login/', {
'username': u'chris',
'password': 'toast',
'next' : '/u/chris/'})
assert_equal(
urlparse.urlsplit(response.location)[2],
'/u/chris/')

View File

@ -30,7 +30,7 @@ from mediagoblin.db.open import setup_connection_and_db_from_config
MEDIAGOBLIN_TEST_DB_NAME = '__mediagoblinunittests__' MEDIAGOBLIN_TEST_DB_NAME = '__mediagoblinunittests__'
TEST_SERVER_CONFIG = pkg_resources.resource_filename( TEST_SERVER_CONFIG = pkg_resources.resource_filename(
'mediagoblin.tests', 'test_server.ini') 'mediagoblin.tests', 'test_paste.ini')
TEST_APP_CONFIG = pkg_resources.resource_filename( TEST_APP_CONFIG = pkg_resources.resource_filename(
'mediagoblin.tests', 'test_mgoblin_app.ini') 'mediagoblin.tests', 'test_mgoblin_app.ini')
TEST_USER_DEV = pkg_resources.resource_filename( TEST_USER_DEV = pkg_resources.resource_filename(

View File

@ -108,10 +108,10 @@ def atom_feed(request):
feed = AtomFeed(request.matchdict['user'], feed = AtomFeed(request.matchdict['user'],
feed_url=request.url, feed_url=request.url,
url=request.host_url) url=request.host_url)
for entry in cursor: for entry in cursor:
feed.add(entry.get('title'), feed.add(entry.get('title'),
entry.get('description'), entry.get('description_html'),
content_type='html', content_type='html',
author=request.matchdict['user'], author=request.matchdict['user'],
updated=entry.get('created'), updated=entry.get('created'),

View File

@ -29,11 +29,11 @@ import jinja2
import translitcodec import translitcodec
from webob import Response, exc from webob import Response, exc
from lxml.html.clean import Cleaner from lxml.html.clean import Cleaner
import markdown
from mediagoblin import mg_globals from mediagoblin import mg_globals
from mediagoblin.db.util import ObjectId from mediagoblin.db.util import ObjectId
TESTS_ENABLED = False TESTS_ENABLED = False
def _activate_testing(): def _activate_testing():
""" """
@ -98,7 +98,7 @@ def get_jinja_env(template_loader, locale):
template_env = jinja2.Environment( template_env = jinja2.Environment(
loader=template_loader, autoescape=True, loader=template_loader, autoescape=True,
extensions=['jinja2.ext.i18n']) extensions=['jinja2.ext.i18n', 'jinja2.ext.autoescape'])
template_env.install_gettext_callables( template_env.install_gettext_callables(
mg_globals.translations.gettext, mg_globals.translations.gettext,
@ -376,6 +376,16 @@ def clean_html(html):
return HTML_CLEANER.clean_html(html) return HTML_CLEANER.clean_html(html)
MARKDOWN_INSTANCE = markdown.Markdown(safe_mode='escape')
def cleaned_markdown_conversion(text):
"""
Take a block of text, run it through MarkDown, and clean its HTML.
"""
return clean_html(MARKDOWN_INSTANCE.convert(text))
SETUP_GETTEXTS = {} SETUP_GETTEXTS = {}
def setup_gettext(locale): def setup_gettext(locale):

View File

@ -43,6 +43,7 @@ setup(
'argparse', 'argparse',
'webtest', 'webtest',
'ConfigObj', 'ConfigObj',
'Markdown',
## For now we're expecting that users will install this from ## For now we're expecting that users will install this from
## their package managers. ## their package managers.
# 'lxml', # 'lxml',