aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorcfdv <caldavis@gmail.com>2011-06-20 12:50:44 -0500
committercfdv <caldavis@gmail.com>2011-06-20 12:50:44 -0500
commit78c0744077b58fb4c20a965fdbbb52055922d793 (patch)
tree8ba6033b3f7d8e19c78f59dca2db8ee97872647c
parentcd95cb6d3d6ffa403be5f2b347862929917aa1e3 (diff)
parent5c441e75ebc4ad63c3a5362d9bc451abe97984d2 (diff)
downloadmediagoblin-78c0744077b58fb4c20a965fdbbb52055922d793.tar.lz
mediagoblin-78c0744077b58fb4c20a965fdbbb52055922d793.tar.xz
mediagoblin-78c0744077b58fb4c20a965fdbbb52055922d793.zip
Merge remote-tracking branch 'origin/master' into is315
-rw-r--r--docs/git.rst28
-rw-r--r--docs/hackinghowto.rst4
-rwxr-xr-xlazyserver.sh2
-rw-r--r--mediagoblin/db/migrations.py16
-rw-r--r--mediagoblin/db/models.py3
-rw-r--r--mediagoblin/edit/views.py13
-rw-r--r--mediagoblin/submit/views.py11
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/media.html4
-rw-r--r--mediagoblin/tests/test_auth.py92
-rw-r--r--mediagoblin/tests/test_paste.ini (renamed from mediagoblin/tests/test_server.ini)0
-rw-r--r--mediagoblin/tests/tools.py2
-rw-r--r--mediagoblin/user_pages/views.py4
-rw-r--r--mediagoblin/util.py14
-rw-r--r--paste.ini (renamed from server.ini)0
-rw-r--r--setup.py1
15 files changed, 160 insertions, 34 deletions
diff --git a/docs/git.rst b/docs/git.rst
index c3f7ccce..2836ecd8 100644
--- a/docs/git.rst
+++ b/docs/git.rst
@@ -108,8 +108,8 @@ Contributing changes
--------------------
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
-send us the changes.
+decided that he is bored with fjords and wants to fix issue 42 (the
+meaning of life bug) and send us the changes.
Slartibartfast has cloned the MediaGoblin repository and his clone
lives on gitorious.
@@ -125,18 +125,18 @@ Slartibartfast does the following:
git fetch --all -p
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
he wants us to see.
4. Slartibartfast pushes his changes to his clone (the remote is named
``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
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:
-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``)::
- git push origin issue_42
+ git push origin bug42_meaning_of_life
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
- ``issue_42`` branch of his publicly available clone.
+ ``bug42_meaning_of_life`` branch of his publicly available clone.
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
adds a comment to the issue tracker specifying the problem and asks
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.
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
The changes show up in the ``master`` branch of the ``gmg`` remote.
-Slartibartfast now deletes his ``issue_42`` branch because he doesn't
-need it anymore.
+Slartibartfast now deletes his ``bug42_meaning_of_life`` branch
+because he doesn't need it anymore.
How to learn git
diff --git a/docs/hackinghowto.rst b/docs/hackinghowto.rst
index fcab5844..911f2340 100644
--- a/docs/hackinghowto.rst
+++ b/docs/hackinghowto.rst
@@ -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
the server independently::
- ./bin/paster serve server.ini --reload
+ ./bin/paster serve paste.ini --reload
Running celeryd
@@ -158,7 +158,7 @@ Running the test suite
Run::
- ./bin/nosetests
+ ./runtests.sh
Running a shell
diff --git a/lazyserver.sh b/lazyserver.sh
index bd93b109..fdb03ba0 100755
--- a/lazyserver.sh
+++ b/lazyserver.sh
@@ -27,4 +27,4 @@ else
exit 1
fi
-CELERY_ALWAYS_EAGER=true $PASTER serve server.ini --reload
+CELERY_ALWAYS_EAGER=true $PASTER serve paste.ini --reload
diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py
index f1f625b7..b87988fe 100644
--- a/mediagoblin/db/migrations.py
+++ b/mediagoblin/db/migrations.py
@@ -14,6 +14,8 @@
# 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/>.
+from mediagoblin.util import cleaned_markdown_conversion
+
from mongokit import DocumentMigration
@@ -33,5 +35,19 @@ class MediaEntryMigration(DocumentMigration):
self.collection.update(
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']
diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py
index 78bec979..b3de7793 100644
--- a/mediagoblin/db/models.py
+++ b/mediagoblin/db/models.py
@@ -75,7 +75,8 @@ class MediaEntry(Document):
'title': unicode,
'slug': unicode,
'created': datetime.datetime,
- 'description': unicode,
+ 'description': unicode, # May contain markdown/up
+ 'description_html': unicode, # May contain plaintext, or HTML
'media_type': unicode,
'media_data': dict, # extra data relevant to this media_type
'plugin_data': dict, # plugins can dump stuff here.
diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py
index 57c9a118..3de71a64 100644
--- a/mediagoblin/edit/views.py
+++ b/mediagoblin/edit/views.py
@@ -17,11 +17,13 @@
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.lib import may_edit_media
from mediagoblin.decorators import require_active_login, get_user_media_entry
+import markdown
+
@get_user_media_entry
@require_active_login
@@ -47,7 +49,14 @@ def edit_media(request, media):
u'An entry with that slug already exists for this user.')
else:
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.save()
diff --git a/mediagoblin/submit/views.py b/mediagoblin/submit/views.py
index e9b5c37e..6139614e 100644
--- a/mediagoblin/submit/views.py
+++ b/mediagoblin/submit/views.py
@@ -19,7 +19,8 @@ from cgi import FieldStorage
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.submit import forms as submit_forms, security
from mediagoblin.process_media import process_media_initial
@@ -46,8 +47,14 @@ def submit_start(request):
# create entry and save in database
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_html'] = cleaned_markdown_conversion(
+ entry['description'])
+
entry['media_type'] = u'image' # heh
entry['uploader'] = request.user['_id']
diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html
index 200f13cd..44bc38b8 100644
--- a/mediagoblin/templates/mediagoblin/user_pages/media.html
+++ b/mediagoblin/templates/mediagoblin/user_pages/media.html
@@ -25,7 +25,9 @@
</h1>
<img class="media_image" src="{{ request.app.public_store.file_url(
media.media_files.main) }}" />
- <p>{{ media.description }}</p>
+ {% autoescape False %}
+ <p>{{ media.description_html }}</p>
+ {% endautoescape %}
<p>Uploaded on
{{ "%4d-%02d-%02d"|format(media.created.year,
media.created.month, media.created.day) }}
diff --git a/mediagoblin/tests/test_auth.py b/mediagoblin/tests/test_auth.py
index b8389f8d..3a13cbb1 100644
--- a/mediagoblin/tests/test_auth.py
+++ b/mediagoblin/tests/test_auth.py
@@ -242,17 +242,69 @@ def test_authentication_views(test_app):
test_user.save()
# Get login
+ # ---------
test_app.get('/auth/login/')
- # Make sure it rendered with the appropriate template
assert util.TEMPLATE_TEST_CONTEXT.has_key(
'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()
response = test_app.post(
'/auth/login/', {
'username': u'chris',
'password': 'toast'})
+
+ # User should be redirected
response.follow()
assert_equal(
urlparse.urlsplit(response.location)[2],
@@ -260,10 +312,38 @@ def test_authentication_views(test_app):
assert util.TEMPLATE_TEST_CONTEXT.has_key(
'mediagoblin/root.html')
- # Make sure we're in the session or something
- session = util.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']['request'].session
+ # Make sure user is in the session
+ context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
+ session = context['request'].session
assert session['user_id'] == unicode(test_user['_id'])
- # Log out as that user
- # Make sure we're not in the session
+ # Successful logout
+ # -----------------
+ 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/')
diff --git a/mediagoblin/tests/test_server.ini b/mediagoblin/tests/test_paste.ini
index 929a1ccf..929a1ccf 100644
--- a/mediagoblin/tests/test_server.ini
+++ b/mediagoblin/tests/test_paste.ini
diff --git a/mediagoblin/tests/tools.py b/mediagoblin/tests/tools.py
index 515bccd4..9e36fc5c 100644
--- a/mediagoblin/tests/tools.py
+++ b/mediagoblin/tests/tools.py
@@ -30,7 +30,7 @@ from mediagoblin.db.open import setup_connection_and_db_from_config
MEDIAGOBLIN_TEST_DB_NAME = '__mediagoblinunittests__'
TEST_SERVER_CONFIG = pkg_resources.resource_filename(
- 'mediagoblin.tests', 'test_server.ini')
+ 'mediagoblin.tests', 'test_paste.ini')
TEST_APP_CONFIG = pkg_resources.resource_filename(
'mediagoblin.tests', 'test_mgoblin_app.ini')
TEST_USER_DEV = pkg_resources.resource_filename(
diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py
index 88b5dfe5..d6cd6034 100644
--- a/mediagoblin/user_pages/views.py
+++ b/mediagoblin/user_pages/views.py
@@ -108,10 +108,10 @@ def atom_feed(request):
feed = AtomFeed(request.matchdict['user'],
feed_url=request.url,
url=request.host_url)
-
+
for entry in cursor:
feed.add(entry.get('title'),
- entry.get('description'),
+ entry.get('description_html'),
content_type='html',
author=request.matchdict['user'],
updated=entry.get('created'),
diff --git a/mediagoblin/util.py b/mediagoblin/util.py
index 349bc027..0e43a1f5 100644
--- a/mediagoblin/util.py
+++ b/mediagoblin/util.py
@@ -29,11 +29,11 @@ import jinja2
import translitcodec
from webob import Response, exc
from lxml.html.clean import Cleaner
+import markdown
from mediagoblin import mg_globals
from mediagoblin.db.util import ObjectId
-
TESTS_ENABLED = False
def _activate_testing():
"""
@@ -98,7 +98,7 @@ def get_jinja_env(template_loader, locale):
template_env = jinja2.Environment(
loader=template_loader, autoescape=True,
- extensions=['jinja2.ext.i18n'])
+ extensions=['jinja2.ext.i18n', 'jinja2.ext.autoescape'])
template_env.install_gettext_callables(
mg_globals.translations.gettext,
@@ -376,6 +376,16 @@ def 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 = {}
def setup_gettext(locale):
diff --git a/server.ini b/paste.ini
index 73fbe8e8..73fbe8e8 100644
--- a/server.ini
+++ b/paste.ini
diff --git a/setup.py b/setup.py
index cd0e7f0b..2a007f4e 100644
--- a/setup.py
+++ b/setup.py
@@ -43,6 +43,7 @@ setup(
'argparse',
'webtest',
'ConfigObj',
+ 'Markdown',
## For now we're expecting that users will install this from
## their package managers.
# 'lxml',