diff options
24 files changed, 254 insertions, 76 deletions
diff --git a/docs/git.rst b/docs/git.rst index 2836ecd8..73e7a311 100644 --- a/docs/git.rst +++ b/docs/git.rst @@ -80,10 +80,10 @@ doing and why. How to send us your changes --------------------------- -There are three ways to let us know how to get it: +There are two ways to let us know how to get it: -1. (preferred) **push changes to publicly available git clone and let - us know where to find it** +1. *(preferred)* **push changes to publicly available git clone and + let us know where to find it** Push your feature/bugfix/issue branch to your publicly available git clone and add a comment to the issue with the url for your @@ -93,14 +93,22 @@ There are three ways to let us know how to get it: Run:: - git format-patch -o patches <remote>/master + git format-patch --stdout <remote>/master > issue_<number>.patch - Then tar up the newly created ``patches`` directory and attach the - directory to the issue. + ``format-patch`` creates a patch of all the commits that are in + your branch that aren't in ``<remote>/master``. The ``--stdout`` + flag causes all this output to go to stdout where it's redirected + to a file named ``issue_<number>.patch``. That file should be + based on the issue you're working with. For example, + ``issue_42.patch`` is a good filename and ``issue_42_rev2.patch`` + is good if you did a revision of it. + + Having said all that, the filename isn't wildly important. Example workflow ================ + Here's an example workflow. @@ -124,20 +132,30 @@ Slartibartfast does the following: git fetch --all -p + This tells ``git fetch`` to fetch all the recent data from all of + the remotes (``--all``) and prune any branches that have been + deleted in the remotes (``-p``). + 2. Creates a branch from the tip of the MediaGoblin repository (the remote is named ``gmg``) master branch called ``bug42_meaning_of_life``:: git checkout -b bug42_meaning_of_life gmg/master + This creates a new branch (``-b``) named ``bug42_meaning_of_life`` based + on the tip of the ``master`` branch of the remote named ``gmg`` and checks + it out. + 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``):: +4. Slartibartfast pushes his changes to his clone:: git push origin bug42_meaning_of_life --set-upstream + This pushes the changes in the ``bug42_meaning_of_life`` branch to the + remote named ``origin``. + 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 explains what he did and why it addresses the issue. diff --git a/docs/mediagoblin.rst b/docs/mediagoblin.rst index ea9c83a7..c437ecc3 100644 --- a/docs/mediagoblin.rst +++ b/docs/mediagoblin.rst @@ -2,6 +2,10 @@ GNU MediaGoblin ================= +.. contents:: Sections + :local: + + What is GNU MediaGoblin ======================= diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index aacbf079..712f8ab4 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -50,5 +50,20 @@ class MediaEntryMigration(DocumentMigration): 'description_html': cleaned_markdown_conversion( doc['description'])}} - -MIGRATE_CLASSES = ['MediaEntry'] +class UserMigration(DocumentMigration): + def allmigration01_add_bio_and_url_profile(self): + """ + User can elaborate profile with home page and biography + """ + self.target = {'url': {'$exists': False}, + 'bio': {'$exists': False}} + if not self.status: + for doc in self.collection.find(self.target): + self.update = { + '$set': {'url': '', + 'bio': ''}} + self.collection.update( + self.target, self.update, multi=True, safe=True) + + +MIGRATE_CLASSES = ['MediaEntry', 'User'] diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index e034cc29..600b79ff 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -46,6 +46,8 @@ class User(Document): 'status': unicode, 'verification_key': unicode, 'is_admin': bool, + 'url' : unicode, + 'bio' : unicode } required_fields = ['username', 'created', 'pw_hash', 'email'] @@ -56,6 +58,8 @@ class User(Document): 'status': u'needs_email_verification', 'verification_key': lambda: unicode(uuid.uuid4()), 'is_admin': False} + + migration_handler = migrations.UserMigration def check_login(self, password): """ diff --git a/mediagoblin/db/util.py b/mediagoblin/db/util.py index 30615fca..470da531 100644 --- a/mediagoblin/db/util.py +++ b/mediagoblin/db/util.py @@ -17,4 +17,5 @@ # Imports that other modules might use from pymongo import DESCENDING +from pymongo.errors import InvalidId from mongokit import ObjectId diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py index c2fe3f9f..081eda62 100644 --- a/mediagoblin/decorators.py +++ b/mediagoblin/decorators.py @@ -15,11 +15,10 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. -from bson.errors import InvalidId from webob import exc from mediagoblin.util import redirect -from mediagoblin.db.util import ObjectId +from mediagoblin.db.util import ObjectId, InvalidId def _make_safe(decorator, original): diff --git a/mediagoblin/edit/forms.py b/mediagoblin/edit/forms.py index ea25141d..2efdb9e4 100644 --- a/mediagoblin/edit/forms.py +++ b/mediagoblin/edit/forms.py @@ -25,3 +25,10 @@ class EditForm(wtforms.Form): slug = wtforms.TextField( 'Slug') description = wtforms.TextAreaField('Description of this work') + +class EditProfileForm(wtforms.Form): + bio = wtforms.TextAreaField('Bio', + [wtforms.validators.Length(min=0, max=500)]) + url = wtforms.TextField( + 'Website', + [wtforms.validators.URL(message='Improperly formed URL')]) diff --git a/mediagoblin/edit/routing.py b/mediagoblin/edit/routing.py index bf0b2498..9771207a 100644 --- a/mediagoblin/edit/routing.py +++ b/mediagoblin/edit/routing.py @@ -19,4 +19,5 @@ from routes.route import Route edit_routes = [ # Media editing view handled in user_pages/routing.py -] + Route('mediagoblin.edit.profile', '/profile/', + controller="mediagoblin.edit.views:edit_profile")] diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py index 6c16a61e..a9071495 100644 --- a/mediagoblin/edit/views.py +++ b/mediagoblin/edit/views.py @@ -68,3 +68,25 @@ def edit_media(request, media): 'mediagoblin/edit/edit.html', {'media': media, 'form': form}) + + +@require_active_login +def edit_profile(request): + + user = request.user + form = forms.EditProfileForm(request.POST, + url = user.get('url'), + bio = user.get('bio')) + + if request.method == 'POST' and form.validate(): + user['url'] = request.POST['url'] + user['bio'] = request.POST['bio'] + user.save() + + return redirect(request, "index", user=user['username']) + + return render_to_response( + request, + 'mediagoblin/edit/edit_profile.html', + {'user': user, + 'form': form}) diff --git a/mediagoblin/static/css/base.css b/mediagoblin/static/css/base.css index 5d928b9a..1d04fc73 100644 --- a/mediagoblin/static/css/base.css +++ b/mediagoblin/static/css/base.css @@ -69,34 +69,39 @@ a.mediagoblin_logo:hover { float:right; } +/* common website elements */ + +.dotted_line { + width:100%; + height:0px; + border-bottom: dotted 1px #5f5f5f; + position:absolute; + left:0px; + margin-top:-20px; +} + .button { font-family:'Carter One', arial, serif; height:32px; min-width:99px; background-color:#86d4b1; + background-image: -webkit-gradient(linear, left top, left bottom, from(#86d4b1), to(#62caa2)); + background-image: -webkit-linear-gradient(top, #86d4b1, #62caa2); + background-image: -moz-linear-gradient(top, #86d4b1, #62caa2); + background-image: -ms-linear-gradient(top, #86d4b1, #62caa2); + background-image: -o-linear-gradient(top, #86d4b1, #62caa2); + background-image: linear-gradient(top, #86d4b1, #62caa2); box-shadow:0px 0px 4px #000; border-radius:5px; border:none; color:#272727; - margin:10px; + margin:10px 0px 10px 15px; font-size:1em; - display:block; text-align:center; padding-left:11px; padding-right:11px; } -/* common website elements */ - -.dotted_line { - width:100%; - height:0px; - border-bottom: dotted 1px #5f5f5f; - position:absolute; - left:0px; - margin-top:-20px; -} - /* forms */ .form_box { @@ -104,8 +109,9 @@ a.mediagoblin_logo:hover { margin-left:auto; margin-right:auto; background-color:#393939; - padding:0px 83px 30px 83px; - border-top:5px solid #d49086; + background-image:url("../images/background_lines.png"); + background-repeat:repeat-x; + padding:1px 83px 30px 83px; font-size:18px; } @@ -113,6 +119,11 @@ a.mediagoblin_logo:hover { width:600px; } +.edit_box { + width:600px; + background-image:url("../images/background_edit.png"); +} + .form_box h1 { font-size:28px; } @@ -139,6 +150,10 @@ a.mediagoblin_logo:hover { margin-bottom:8px; } +.form_submit_buttons { + text-align:right; +} + /* media pages */ img.media_image { @@ -147,14 +162,17 @@ img.media_image { margin-right:auto; } +ul.media_thumbnail { + padding:0px; +} + li.media_thumbnail { - width: 200px; - min-height: 250px; - display: -moz-inline-stack; - display: inline-block; - vertical-align: top; - margin: 5px; - zoom: 1; - *display: inline; - _height: 250px; + width:200px; + height:133px; + display:-moz-inline-stack; + display:inline-block; + vertical-align:top; + margin:0px 10px 10px 0px; + zoom:1; +. *display:inline; } diff --git a/mediagoblin/static/images/background_edit.png b/mediagoblin/static/images/background_edit.png Binary files differnew file mode 100644 index 00000000..4071fd53 --- /dev/null +++ b/mediagoblin/static/images/background_edit.png diff --git a/mediagoblin/static/images/background_lines.png b/mediagoblin/static/images/background_lines.png Binary files differnew file mode 100644 index 00000000..e1b07afe --- /dev/null +++ b/mediagoblin/static/images/background_lines.png diff --git a/mediagoblin/templates/mediagoblin/base.html b/mediagoblin/templates/mediagoblin/base.html index c7313173..8e5fd55b 100644 --- a/mediagoblin/templates/mediagoblin/base.html +++ b/mediagoblin/templates/mediagoblin/base.html @@ -35,6 +35,8 @@ <div class="mediagoblin_header_right"> {% if request.user %} {{ request.user['username'] }}'s account + <a href="{{ request.urlgen('mediagoblin.user_pages.user_home', + user= request.user['username']) }}">home</a> <a href="{{ request.urlgen('mediagoblin.user_pages.user_gallery', user= request.user['username']) }}">gallery</a> (<a href="{{ request.urlgen('mediagoblin.auth.logout') }}">logout</a>) diff --git a/mediagoblin/templates/mediagoblin/edit/edit.html b/mediagoblin/templates/mediagoblin/edit/edit.html index 295d57eb..51d0341d 100644 --- a/mediagoblin/templates/mediagoblin/edit/edit.html +++ b/mediagoblin/templates/mediagoblin/edit/edit.html @@ -20,19 +20,21 @@ {% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} {% block mediagoblin_content %} - <h1>Edit details for {{ media.title }}</h1> <form action="{{ request.urlgen('mediagoblin.edit.edit_media', user= media.uploader().username, media= media._id) }}" method="POST" enctype="multipart/form-data"> - <div class="submit_box form_box"> + <div class="edit_box form_box"> + <h1>Editing {{ media.title }}</h1> {{ wtforms_util.render_divs(form) }} <div class="form_submit_buttons"> - <input type="submit" value="submit" class="button" /> + <a href="{{ media.url_for_self(request.urlgen) }}">Cancel</a> + <input type="submit" value="Save changes" class="button" /> </div> + <img src="{{ request.app.public_store.file_url( + media['media_files']['thumb']) }}" /> </div> </form> - <img src="{{ request.app.public_store.file_url( - media['media_files']['thumb']) }}" /> + {% endblock %} diff --git a/mediagoblin/templates/mediagoblin/edit/edit_profile.html b/mediagoblin/templates/mediagoblin/edit/edit_profile.html new file mode 100644 index 00000000..8ce474f0 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/edit/edit_profile.html @@ -0,0 +1,35 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 Free Software Foundation, Inc +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# 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/>. +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_content %} + + <form action="{{ request.urlgen('mediagoblin.edit.profile', + user=user.username) }}" + method="POST" enctype="multipart/form-data"> + <div class="edit_box form_box"> + <h1>Editing {{ user['username'] }}'s profile</h1> + {{ wtforms_util.render_divs(form) }} + <div class="form_submit_buttons"> + <input type="submit" value="submit" class="button" /> + </div> + </div> + </form> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/root.html b/mediagoblin/templates/mediagoblin/root.html index e5344e08..7261f4fc 100644 --- a/mediagoblin/templates/mediagoblin/root.html +++ b/mediagoblin/templates/mediagoblin/root.html @@ -22,20 +22,19 @@ <h1>{% trans %}Welcome to GNU MediaGoblin!{% endtrans %}</h1> {% if request.user %} - <p> - <a href="{{ request.urlgen('mediagoblin.submit.start') }}">Submit an item</a>. - </p> - + <p> + <a href="{{ request.urlgen('mediagoblin.submit.start') }}">Submit an item</a> + <a href="{{ request.urlgen('mediagoblin.edit.profile') }}">Edit profile</a> + </p> {% else %} - <p> - If you have an account, you can - <a href="{{ request.urlgen('mediagoblin.auth.login') }}">Login</a>. - </p> - <p> - If you don't have an account, please - <a href="{{ request.urlgen('mediagoblin.auth.register') }}">Register</a>. - </p> - + <p> + If you have an account, you can + <a href="{{ request.urlgen('mediagoblin.auth.login') }}">Login</a>. + </p> + <p> + If you don't have an account, please + <a href="{{ request.urlgen('mediagoblin.auth.register') }}">Register</a>. + </p> {% endif %} {# temporarily, an "image gallery" that isn't one really ;) #} diff --git a/mediagoblin/templates/mediagoblin/submit/start.html b/mediagoblin/templates/mediagoblin/submit/start.html index 75c31df4..fe1f3369 100644 --- a/mediagoblin/templates/mediagoblin/submit/start.html +++ b/mediagoblin/templates/mediagoblin/submit/start.html @@ -27,7 +27,7 @@ <h1>Submit yer media</h1> {{ wtforms_util.render_divs(submit_form) }} <div class="form_submit_buttons"> - <input type="submit" value="submit" class="button" /> + <input type="submit" value="Submit" class="button" /> </div> </div> </form> diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html index 44bc38b8..d221f61e 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media.html @@ -34,10 +34,12 @@ by <a href="{{ request.urlgen('mediagoblin.user_pages.user_home', user= media.uploader().username) }}"> - {{- media.uploader().username }}</a></p> - <p><a href="{{ request.urlgen('mediagoblin.edit.edit_media', - user= media.uploader().username, - media= media._id) }}">Edit</a></p> + {{- media.uploader().username }}</a></p> + {% if media['uploader'] == request.user['_id'] %} + <p><a href="{{ request.urlgen('mediagoblin.edit.edit_media', + user= media.uploader().username, + media= media._id) }}">Edit</a></p> + {% endif %} {% else %} <p>Sorry, no such media found.<p/> {% endif %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/user.html b/mediagoblin/templates/mediagoblin/user_pages/user.html index b3708c85..f7a9f3c9 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/user.html +++ b/mediagoblin/templates/mediagoblin/user_pages/user.html @@ -27,6 +27,8 @@ {% block mediagoblin_content -%} {% if user %} <h1>User page for '{{ user.username }}'</h1> + + {% include "mediagoblin/utils/profile.html" %} {% include "mediagoblin/utils/object_gallery.html" %} diff --git a/mediagoblin/templates/mediagoblin/utils/object_gallery.html b/mediagoblin/templates/mediagoblin/utils/object_gallery.html index c9c3e0db..8c88c174 100644 --- a/mediagoblin/templates/mediagoblin/utils/object_gallery.html +++ b/mediagoblin/templates/mediagoblin/utils/object_gallery.html @@ -19,7 +19,7 @@ {% block object_gallery_content -%} <div> {% if media_entries %} - <ul> + <ul class="media_thumbnail"> {% for entry in media_entries %} <li class="media_thumbnail"> <a href="{{ entry.url_for_self(request.urlgen) }}"> diff --git a/mediagoblin/templates/mediagoblin/utils/profile.html b/mediagoblin/templates/mediagoblin/utils/profile.html new file mode 100644 index 00000000..b3f5f0f8 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/utils/profile.html @@ -0,0 +1,35 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 Free Software Foundation, Inc +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# 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/>. +#} + +{% block profile_content -%} + <div> + <ul> + {% if user.url %} + <li> + <a href="{{ user.url }}">homepage</a> + </li> + {% endif %} + + {% if user.bio %} + <li> + {{ user.bio }} + </li> + {% endif %} + </ul> + </div> +{% endblock %} diff --git a/mediagoblin/tests/__init__.py b/mediagoblin/tests/__init__.py index 1f1e23e9..adb6a1b3 100644 --- a/mediagoblin/tests/__init__.py +++ b/mediagoblin/tests/__init__.py @@ -16,12 +16,17 @@ from mediagoblin import mg_globals +from mediagoblin.tests.tools import ( + MEDIAGOBLIN_TEST_DB_NAME, suicide_if_bad_celery_environ) + def setup_package(): - pass + suicide_if_bad_celery_environ() + def teardown_package(): - if mg_globals.db_connection: - print "Killing db ..." - mg_globals.db_connection.drop_database(mg_globals.database.name) - print "... done" + if ((mg_globals.db_connection + and mg_globals.database.name == MEDIAGOBLIN_TEST_DB_NAME)): + print "Killing db ..." + mg_globals.db_connection.drop_database(MEDIAGOBLIN_TEST_DB_NAME) + print "... done" diff --git a/mediagoblin/tests/tools.py b/mediagoblin/tests/tools.py index 9e36fc5c..ebb5f1b5 100644 --- a/mediagoblin/tests/tools.py +++ b/mediagoblin/tests/tools.py @@ -28,7 +28,7 @@ from mediagoblin.decorators import _make_safe from mediagoblin.db.open import setup_connection_and_db_from_config -MEDIAGOBLIN_TEST_DB_NAME = '__mediagoblinunittests__' +MEDIAGOBLIN_TEST_DB_NAME = u'__mediagoblin_tests__' TEST_SERVER_CONFIG = pkg_resources.resource_filename( 'mediagoblin.tests', 'test_paste.ini') TEST_APP_CONFIG = pkg_resources.resource_filename( @@ -42,17 +42,23 @@ USER_DEV_DIRECTORIES_TO_SETUP = [ 'media/public', 'media/queue', 'beaker/sessions/data', 'beaker/sessions/lock'] +BAD_CELERY_MESSAGE = """\ +Sorry, you *absolutely* must run nosetests with the +mediagoblin.celery_setup.from_tests module. Like so: +$ CELERY_CONFIG_MODULE=mediagoblin.celery_setup.from_tests ./bin/nosetests""" + class BadCeleryEnviron(Exception): pass -def get_test_app(dump_old_app=True): +def suicide_if_bad_celery_environ(): if not os.environ.get('CELERY_CONFIG_MODULE') == \ 'mediagoblin.celery_setup.from_tests': - raise BadCeleryEnviron( - u"Sorry, you *absolutely* must run nosetests with the\n" - u"mediagoblin.celery_setup.from_tests module. Like so:\n" - u"$ CELERY_CONFIG_MODULE=mediagoblin.celery_setup.from_tests ./bin/nosetests") + raise BadCeleryEnviron(BAD_CELERY_MESSAGE) + + +def get_test_app(dump_old_app=True): + suicide_if_bad_celery_environ() global MGOBLIN_APP global CELERY_SETUP @@ -78,6 +84,7 @@ def get_test_app(dump_old_app=True): # @@: For now we're dropping collections, but we could also just # collection.remove() ? connection, db = setup_connection_and_db_from_config(app_config) + assert db.name == MEDIAGOBLIN_TEST_DB_NAME collections_to_wipe = [ collection @@ -87,10 +94,6 @@ def get_test_app(dump_old_app=True): for collection in collections_to_wipe: db.drop_collection(collection) - # Don't need these anymore... - del(connection) - del(db) - # TODO: Drop and recreate indexes # setup app and return diff --git a/mediagoblin/util.py b/mediagoblin/util.py index e964324f..91fbee0a 100644 --- a/mediagoblin/util.py +++ b/mediagoblin/util.py @@ -373,6 +373,10 @@ HTML_CLEANER = Cleaner( def clean_html(html): + # clean_html barfs on an empty string + if not html: + return u'' + return HTML_CLEANER.clean_html(html) |