Merge remote-tracking branch 'origin/master' into is315
This commit is contained in:
commit
78c0744077
28
docs/git.rst
28
docs/git.rst
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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']
|
||||||
|
@ -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.
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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']
|
||||||
|
|
||||||
|
@ -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) }}
|
||||||
|
@ -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/')
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -111,7 +111,7 @@ def atom_feed(request):
|
|||||||
|
|
||||||
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'),
|
||||||
|
@ -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):
|
||||||
|
1
setup.py
1
setup.py
@ -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',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user