aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mediagoblin/app.py4
-rw-r--r--mediagoblin/db/models.py73
-rw-r--r--mediagoblin/db/open.py13
-rw-r--r--mediagoblin/decorators.py20
-rw-r--r--mediagoblin/edit/routing.py2
-rw-r--r--mediagoblin/edit/views.py47
-rw-r--r--mediagoblin/init/__init__.py2
-rw-r--r--mediagoblin/media_types/ascii/processing.py13
-rw-r--r--mediagoblin/media_types/audio/processing.py24
-rw-r--r--mediagoblin/media_types/image/processing.py13
-rw-r--r--mediagoblin/media_types/stl/processing.py16
-rw-r--r--mediagoblin/media_types/video/processing.py69
-rw-r--r--mediagoblin/static/css/base.css29
-rw-r--r--mediagoblin/static/js/header_dropdown.js27
-rw-r--r--mediagoblin/templates/mediagoblin/base.html52
-rw-r--r--mediagoblin/templates/mediagoblin/edit/delete_account.html48
-rw-r--r--mediagoblin/templates/mediagoblin/edit/edit.html2
-rw-r--r--mediagoblin/templates/mediagoblin/edit/edit_account.html5
-rw-r--r--mediagoblin/templates/mediagoblin/root.html27
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/collection.html4
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/collection_list.html56
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/media.html33
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html2
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/user.html6
-rw-r--r--mediagoblin/tests/test_api.py4
-rw-r--r--mediagoblin/tests/test_auth.py10
-rw-r--r--mediagoblin/tests/test_collections.py37
-rw-r--r--mediagoblin/tests/test_csrf_middleware.py8
-rw-r--r--mediagoblin/tests/test_edit.py22
-rw-r--r--mediagoblin/tests/test_http_callback.py4
-rw-r--r--mediagoblin/tests/test_messages.py4
-rw-r--r--mediagoblin/tests/test_misc.py4
-rw-r--r--mediagoblin/tests/test_oauth.py4
-rw-r--r--mediagoblin/tests/test_submission.py20
-rw-r--r--mediagoblin/tests/test_tags.py4
-rw-r--r--mediagoblin/tests/test_tests.py10
-rw-r--r--mediagoblin/tests/test_workbench.py36
-rw-r--r--mediagoblin/tests/tools.py27
-rw-r--r--mediagoblin/tools/workbench.py (renamed from mediagoblin/workbench.py)31
-rw-r--r--mediagoblin/user_pages/routing.py12
-rw-r--r--mediagoblin/user_pages/views.py44
41 files changed, 638 insertions, 230 deletions
diff --git a/mediagoblin/app.py b/mediagoblin/app.py
index c1636693..10fbf4a3 100644
--- a/mediagoblin/app.py
+++ b/mediagoblin/app.py
@@ -22,6 +22,7 @@ from mediagoblin.tools.routing import endpoint_to_controller
from werkzeug.wrappers import Request
from werkzeug.exceptions import HTTPException, NotFound
+from werkzeug.routing import RequestRedirect
from mediagoblin import meddleware, __version__
from mediagoblin.tools import common, translate, template
@@ -186,6 +187,9 @@ class MediaGoblinApp(object):
try:
found_rule, url_values = map_adapter.match(return_rule=True)
request.matchdict = url_values
+ except RequestRedirect as response:
+ # Deal with 301 responses eg due to missing final slash
+ return response(environ, start_response)
except HTTPException as exc:
# Stop and render exception
return render_http_exception(
diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py
index ea915ae5..782bf869 100644
--- a/mediagoblin/db/models.py
+++ b/mediagoblin/db/models.py
@@ -18,7 +18,7 @@
TODO: indexes on foreignkeys, where useful.
"""
-
+import logging
import datetime
import sys
@@ -34,6 +34,7 @@ from sqlalchemy.util import memoized_property
from mediagoblin.db.extratypes import PathTupleWithSlashes, JSONEncoded
from mediagoblin.db.base import Base, DictReadAttrProxy, Session
from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin, CollectionMixin, CollectionItemMixin
+from mediagoblin.tools.files import delete_media_files
# It's actually kind of annoying how sqlalchemy-migrate does this, if
# I understand it right, but whatever. Anyway, don't remove this :P
@@ -42,6 +43,8 @@ from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin,
# this import-based meddling...
from migrate import changeset
+_log = logging.getLogger(__name__)
+
class User(Base, UserMixin):
"""
@@ -78,6 +81,27 @@ class User(Base, UserMixin):
'admin' if self.is_admin else 'user',
self.username)
+ def delete(self, **kwargs):
+ """Deletes a User and all related entries/comments/files/..."""
+ # Delete this user's Collections and all contained CollectionItems
+ for collection in self.collections:
+ collection.delete(commit=False)
+
+ media_entries = MediaEntry.query.filter(MediaEntry.uploader == self.id)
+ for media in media_entries:
+ # TODO: Make sure that "MediaEntry.delete()" also deletes
+ # all related files/Comments
+ media.delete(del_orphan_tags=False, commit=False)
+
+ # Delete now unused tags
+ # TODO: import here due to cyclic imports!!! This cries for refactoring
+ from mediagoblin.db.util import clean_orphan_tags
+ clean_orphan_tags(commit=False)
+
+ # Delete user, pass through commit=False/True in kwargs
+ super(User, self).delete(**kwargs)
+ _log.info('Deleted user "{0}" account'.format(self.username))
+
class MediaEntry(Base, MediaEntryMixin):
"""
@@ -122,7 +146,6 @@ class MediaEntry(Base, MediaEntryMixin):
)
attachment_files_helper = relationship("MediaAttachmentFile",
- cascade="all, delete-orphan",
order_by="MediaAttachmentFile.created"
)
attachment_files = association_proxy("attachment_files_helper", "dict_view",
@@ -131,7 +154,7 @@ class MediaEntry(Base, MediaEntryMixin):
)
tags_helper = relationship("MediaTag",
- cascade="all, delete-orphan"
+ cascade="all, delete-orphan" # should be automatically deleted
)
tags = association_proxy("tags_helper", "dict_view",
creator=lambda v: MediaTag(name=v["name"], slug=v["slug"])
@@ -216,6 +239,37 @@ class MediaEntry(Base, MediaEntryMixin):
id=self.id,
title=safe_title)
+ def delete(self, del_orphan_tags=True, **kwargs):
+ """Delete MediaEntry and all related files/attachments/comments
+
+ This will *not* automatically delete unused collections, which
+ can remain empty...
+
+ :param del_orphan_tags: True/false if we delete unused Tags too
+ :param commit: True/False if this should end the db transaction"""
+ # User's CollectionItems are automatically deleted via "cascade".
+ # Delete all the associated comments
+ for comment in self.get_comments():
+ comment.delete(commit=False)
+
+ # Delete all related files/attachments
+ try:
+ delete_media_files(self)
+ except OSError, error:
+ # Returns list of files we failed to delete
+ _log.error('No such files from the user "{1}" to delete: '
+ '{0}'.format(str(error), self.get_uploader))
+ _log.info('Deleted Media entry id "{0}"'.format(self.id))
+ # Related MediaTag's are automatically cleaned, but we might
+ # want to clean out unused Tag's too.
+ if del_orphan_tags:
+ # TODO: Import here due to cyclic imports!!!
+ # This cries for refactoring
+ from mediagoblin.db.util import clean_orphan_tags
+ clean_orphan_tags(commit=False)
+ # pass through commit=False/True in kwargs
+ super(MediaEntry, self).delete(**kwargs)
+
class FileKeynames(Base):
"""
@@ -344,6 +398,10 @@ class MediaComment(Base, MediaCommentMixin):
class Collection(Base, CollectionMixin):
+ """An 'album' or 'set' of media by a user.
+
+ On deletion, contained CollectionItems get automatically reaped via
+ SQL cascade"""
__tablename__ = "core__collections"
id = Column(Integer, primary_key=True)
@@ -353,11 +411,13 @@ class Collection(Base, CollectionMixin):
index=True)
description = Column(UnicodeText)
creator = Column(Integer, ForeignKey(User.id), nullable=False)
+ # TODO: No of items in Collection. Badly named, can we migrate to num_items?
items = Column(Integer, default=0)
- get_creator = relationship(User)
+ get_creator = relationship(User, backref="collections")
def get_collection_items(self, ascending=False):
+ #TODO, is this still needed with self.collection_items being available?
order_col = CollectionItem.position
if not ascending:
order_col = desc(order_col)
@@ -375,7 +435,10 @@ class CollectionItem(Base, CollectionItemMixin):
note = Column(UnicodeText, nullable=True)
added = Column(DateTime, nullable=False, default=datetime.datetime.now)
position = Column(Integer)
- in_collection = relationship("Collection")
+ in_collection = relationship("Collection",
+ backref=backref(
+ "collection_items",
+ cascade="all, delete-orphan"))
get_media_entry = relationship(MediaEntry)
diff --git a/mediagoblin/db/open.py b/mediagoblin/db/open.py
index d976acd8..5fd5ed03 100644
--- a/mediagoblin/db/open.py
+++ b/mediagoblin/db/open.py
@@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-from sqlalchemy import create_engine
+from sqlalchemy import create_engine, event
import logging
from mediagoblin.db.base import Base, Session
@@ -66,9 +66,20 @@ def load_models(app_config):
exc))
+def _sqlite_fk_pragma_on_connect(dbapi_con, con_record):
+ """Enable foreign key checking on each new sqlite connection"""
+ dbapi_con.execute('pragma foreign_keys=on')
+
+
def setup_connection_and_db_from_config(app_config):
engine = create_engine(app_config['sql_engine'])
+
+ # Enable foreign key checking for sqlite
+ if app_config['sql_engine'].startswith('sqlite://'):
+ event.listen(engine, 'connect', _sqlite_fk_pragma_on_connect)
+
# logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
+
Session.configure(bind=engine)
return DatabaseMaster(engine)
diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py
index 5533e81d..804fab7e 100644
--- a/mediagoblin/decorators.py
+++ b/mediagoblin/decorators.py
@@ -20,6 +20,7 @@ from urlparse import urljoin
from werkzeug.exceptions import Forbidden, NotFound
from werkzeug.urls import url_quote
+from mediagoblin import mg_globals as mgg
from mediagoblin.db.models import MediaEntry, User
from mediagoblin.tools.response import redirect, render_404
@@ -69,7 +70,7 @@ def user_may_delete_media(controller):
"""
@wraps(controller)
def wrapper(request, *args, **kwargs):
- uploader_id = MediaEntry.query.get(request.matchdict['media']).uploader
+ uploader_id = kwargs['media'].uploader
if not (request.user.is_admin or
request.user.id == uploader_id):
raise Forbidden()
@@ -209,12 +210,27 @@ def get_media_entry_by_id(controller):
@wraps(controller)
def wrapper(request, *args, **kwargs):
media = MediaEntry.query.filter_by(
- id=request.matchdict['media'],
+ id=request.matchdict['media_id'],
state=u'processed').first()
# Still no media? Okay, 404.
if not media:
return render_404(request)
+ given_username = request.matchdict.get('user')
+ if given_username and (given_username != media.get_uploader.username):
+ return render_404(request)
+
return controller(request, media=media, *args, **kwargs)
return wrapper
+
+
+def get_workbench(func):
+ """Decorator, passing in a workbench as kwarg which is cleaned up afterwards"""
+
+ @wraps(func)
+ def new_func(*args, **kwargs):
+ with mgg.workbench_manager.create() as workbench:
+ return func(*args, workbench=workbench, **kwargs)
+
+ return new_func
diff --git a/mediagoblin/edit/routing.py b/mediagoblin/edit/routing.py
index d382e549..035a766f 100644
--- a/mediagoblin/edit/routing.py
+++ b/mediagoblin/edit/routing.py
@@ -22,3 +22,5 @@ add_route('mediagoblin.edit.legacy_edit_profile', '/edit/profile/',
'mediagoblin.edit.views:legacy_edit_profile')
add_route('mediagoblin.edit.account', '/edit/account/',
'mediagoblin.edit.views:edit_account')
+add_route('mediagoblin.edit.delete_account', '/edit/account/delete/',
+ 'mediagoblin.edit.views:delete_account')
diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py
index ece11df5..3beeae8d 100644
--- a/mediagoblin/edit/views.py
+++ b/mediagoblin/edit/views.py
@@ -27,17 +27,19 @@ from mediagoblin.auth import lib as auth_lib
from mediagoblin.edit import forms
from mediagoblin.edit.lib import may_edit_media
from mediagoblin.decorators import (require_active_login, active_user_from_url,
+ get_media_entry_by_id,
get_user_media_entry, user_may_alter_collection, get_user_collection)
from mediagoblin.tools.response import render_to_response, redirect
from mediagoblin.tools.translate import pass_to_ugettext as _
from mediagoblin.tools.text import (
convert_to_tag_list_of_dicts, media_tags_as_string)
+from mediagoblin.tools.url import slugify
from mediagoblin.db.util import check_media_slug_used, check_collection_slug_used
import mimetypes
-@get_user_media_entry
+@get_media_entry_by_id
@require_active_login
def edit_media(request, media):
if not may_edit_media(request, media):
@@ -57,22 +59,20 @@ def edit_media(request, media):
if request.method == 'POST' and form.validate():
# Make sure there isn't already a MediaEntry with such a slug
# and userid.
- slug_used = check_media_slug_used(media.uploader, request.form['slug'],
- media.id)
+ slug = slugify(request.form['slug'])
+ slug_used = check_media_slug_used(media.uploader, slug, media.id)
if slug_used:
form.slug.errors.append(
_(u'An entry with that slug already exists for this user.'))
else:
- media.title = unicode(request.form['title'])
- media.description = unicode(request.form.get('description'))
+ media.title = request.form['title']
+ media.description = request.form.get('description')
media.tags = convert_to_tag_list_of_dicts(
request.form.get('tags'))
media.license = unicode(request.form.get('license', '')) or None
-
- media.slug = unicode(request.form['slug'])
-
+ media.slug = slug
media.save()
return redirect(request,
@@ -266,6 +266,37 @@ def edit_account(request):
@require_active_login
+def delete_account(request):
+ """Delete a user completely"""
+ user = request.user
+ if request.method == 'POST':
+ if request.form.get(u'confirmed'):
+ # Form submitted and confirmed. Actually delete the user account
+ # Log out user and delete cookies etc.
+ # TODO: Should we be using MG.auth.views.py:logout for this?
+ request.session.delete()
+
+ # Delete user account and all related media files etc....
+ request.user.delete()
+
+ # We should send a message that the user has been deleted
+ # successfully. But we just deleted the session, so we
+ # can't...
+ return redirect(request, 'index')
+
+ else: # Did not check the confirmation box...
+ messages.add_message(
+ request, messages.WARNING,
+ _('You need to confirm the deletion of your account.'))
+
+ # No POST submission or not confirmed, just show page
+ return render_to_response(
+ request,
+ 'mediagoblin/edit/delete_account.html',
+ {'user': user})
+
+
+@require_active_login
@user_may_alter_collection
@get_user_collection
def edit_collection(request, collection):
diff --git a/mediagoblin/init/__init__.py b/mediagoblin/init/__init__.py
index ab6e6399..7c832442 100644
--- a/mediagoblin/init/__init__.py
+++ b/mediagoblin/init/__init__.py
@@ -26,7 +26,7 @@ from mediagoblin import mg_globals
from mediagoblin.mg_globals import setup_globals
from mediagoblin.db.open import setup_connection_and_db_from_config, \
check_db_migrations_current, load_models
-from mediagoblin.workbench import WorkbenchManager
+from mediagoblin.tools.workbench import WorkbenchManager
from mediagoblin.storage import storage_system_from_config
diff --git a/mediagoblin/media_types/ascii/processing.py b/mediagoblin/media_types/ascii/processing.py
index 04d1166c..254717eb 100644
--- a/mediagoblin/media_types/ascii/processing.py
+++ b/mediagoblin/media_types/ascii/processing.py
@@ -19,6 +19,7 @@ import Image
import logging
from mediagoblin import mg_globals as mgg
+from mediagoblin.decorators import get_workbench
from mediagoblin.processing import create_pub_filepath
from mediagoblin.media_types.ascii import asciitoimage
@@ -38,12 +39,14 @@ def sniff_handler(media_file, **kw):
return False
-def process_ascii(entry):
- '''
- Code to process a txt file
- '''
+@get_workbench
+def process_ascii(entry, workbench=None):
+ """Code to process a txt file. Will be run by celery.
+
+ A Workbench() represents a local tempory dir. It is automatically
+ cleaned up when this function exits.
+ """
ascii_config = mgg.global_config['media_type:mediagoblin.media_types.ascii']
- workbench = mgg.workbench_manager.create_workbench()
# Conversions subdirectory to avoid collisions
conversions_subdir = os.path.join(
workbench.dir, 'conversions')
diff --git a/mediagoblin/media_types/audio/processing.py b/mediagoblin/media_types/audio/processing.py
index aee843d5..e12cefe6 100644
--- a/mediagoblin/media_types/audio/processing.py
+++ b/mediagoblin/media_types/audio/processing.py
@@ -15,10 +15,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
-import tempfile
+from tempfile import NamedTemporaryFile
import os
from mediagoblin import mg_globals as mgg
+from mediagoblin.decorators import get_workbench
from mediagoblin.processing import (create_pub_filepath, BadMediaFail,
FilenameBuilder, ProgressCallback)
@@ -42,10 +43,14 @@ def sniff_handler(media_file, **kw):
return False
-def process_audio(entry):
- audio_config = mgg.global_config['media_type:mediagoblin.media_types.audio']
+@get_workbench
+def process_audio(entry, workbench=None):
+ """Code to process uploaded audio. Will be run by celery.
- workbench = mgg.workbench_manager.create_workbench()
+ A Workbench() represents a local tempory dir. It is automatically
+ cleaned up when this function exits.
+ """
+ audio_config = mgg.global_config['media_type:mediagoblin.media_types.audio']
queued_filepath = entry.queued_media_file
queued_filename = workbench.localized_file(
@@ -73,7 +78,7 @@ def process_audio(entry):
transcoder = AudioTranscoder()
- with tempfile.NamedTemporaryFile() as webm_audio_tmp:
+ with NamedTemporaryFile(dir=workbench.dir) as webm_audio_tmp:
progress_callback = ProgressCallback(entry)
transcoder.transcode(
@@ -99,7 +104,7 @@ def process_audio(entry):
original=os.path.splitext(
queued_filepath[-1])[0]))
- with tempfile.NamedTemporaryFile(suffix='.ogg') as wav_tmp:
+ with NamedTemporaryFile(dir=workbench.dir, suffix='.ogg') as wav_tmp:
_log.info('Creating OGG source for spectrogram')
transcoder.transcode(
queued_filename,
@@ -109,7 +114,7 @@ def process_audio(entry):
thumbnailer = AudioThumbnailer()
- with tempfile.NamedTemporaryFile(suffix='.jpg') as spectrogram_tmp:
+ with NamedTemporaryFile(dir=workbench.dir, suffix='.jpg') as spectrogram_tmp:
thumbnailer.spectrogram(
wav_tmp.name,
spectrogram_tmp.name,
@@ -122,7 +127,7 @@ def process_audio(entry):
entry.media_files['spectrogram'] = spectrogram_filepath
- with tempfile.NamedTemporaryFile(suffix='.jpg') as thumb_tmp:
+ with NamedTemporaryFile(dir=workbench.dir, suffix='.jpg') as thumb_tmp:
thumbnailer.thumbnail_spectrogram(
spectrogram_tmp.name,
thumb_tmp.name,
@@ -143,6 +148,3 @@ def process_audio(entry):
entry.media_files['thumb'] = ['fake', 'thumb', 'path.jpg']
mgg.queue_store.delete_file(queued_filepath)
-
- # clean up workbench
- workbench.destroy_self()
diff --git a/mediagoblin/media_types/image/processing.py b/mediagoblin/media_types/image/processing.py
index bf464069..e6a34ca0 100644
--- a/mediagoblin/media_types/image/processing.py
+++ b/mediagoblin/media_types/image/processing.py
@@ -19,6 +19,7 @@ import os
import logging
from mediagoblin import mg_globals as mgg
+from mediagoblin.decorators import get_workbench
from mediagoblin.processing import BadMediaFail, \
create_pub_filepath, FilenameBuilder
from mediagoblin.tools.exif import exif_fix_image_orientation, \
@@ -76,11 +77,13 @@ def sniff_handler(media_file, **kw):
return False
-def process_image(entry):
- """
- Code to process an image
+@get_workbench
+def process_image(entry, workbench=None):
+ """Code to process an image. Will be run by celery.
+
+ A Workbench() represents a local tempory dir. It is automatically
+ cleaned up when this function exits.
"""
- workbench = mgg.workbench_manager.create_workbench()
# Conversions subdirectory to avoid collisions
conversions_subdir = os.path.join(
workbench.dir, 'conversions')
@@ -147,8 +150,6 @@ def process_image(entry):
gps_data['gps_' + key] = gps_data.pop(key)
entry.media_data_init(**gps_data)
- # clean up workbench
- workbench.destroy_self()
if __name__ == '__main__':
import sys
diff --git a/mediagoblin/media_types/stl/processing.py b/mediagoblin/media_types/stl/processing.py
index cd949e2a..3089f295 100644
--- a/mediagoblin/media_types/stl/processing.py
+++ b/mediagoblin/media_types/stl/processing.py
@@ -21,6 +21,7 @@ import subprocess
import pkg_resources
from mediagoblin import mg_globals as mgg
+from mediagoblin.decorators import get_workbench
from mediagoblin.processing import create_pub_filepath, \
FilenameBuilder
@@ -75,11 +76,13 @@ def blender_render(config):
env=env)
-def process_stl(entry):
- """
- Code to process an stl or obj model.
+@get_workbench
+def process_stl(entry, workbench=None):
+ """Code to process an stl or obj model. Will be run by celery.
+
+ A Workbench() represents a local tempory dir. It is automatically
+ cleaned up when this function exits.
"""
- workbench = mgg.workbench_manager.create_workbench()
queued_filepath = entry.queued_media_file
queued_filename = workbench.localized_file(
mgg.queue_store, queued_filepath, 'source')
@@ -164,7 +167,7 @@ def process_stl(entry):
# Remove queued media file from storage and database
mgg.queue_store.delete_file(queued_filepath)
entry.queued_media_file = []
-
+
# Insert media file information into database
media_files_dict = entry.setdefault('media_files', {})
media_files_dict[u'original'] = model_filepath
@@ -185,6 +188,3 @@ def process_stl(entry):
"file_type" : ext,
}
entry.media_data_init(**dimensions)
-
- # clean up workbench
- workbench.destroy_self()
diff --git a/mediagoblin/media_types/video/processing.py b/mediagoblin/media_types/video/processing.py
index aa6a25df..4c9f0131 100644
--- a/mediagoblin/media_types/video/processing.py
+++ b/mediagoblin/media_types/video/processing.py
@@ -14,10 +14,11 @@
# 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/>.
-import tempfile
+from tempfile import NamedTemporaryFile
import logging
from mediagoblin import mg_globals as mgg
+from mediagoblin.decorators import get_workbench
from mediagoblin.processing import \
create_pub_filepath, FilenameBuilder, BaseProcessingFail, ProgressCallback
from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
@@ -51,16 +52,17 @@ def sniff_handler(media_file, **kw):
return False
-
-def process_video(entry):
+@get_workbench
+def process_video(entry, workbench=None):
"""
Process a video entry, transcode the queued media files (originals) and
create a thumbnail for the entry.
+
+ A Workbench() represents a local tempory dir. It is automatically
+ cleaned up when this function exits.
"""
video_config = mgg.global_config['media_type:mediagoblin.media_types.video']
- workbench = mgg.workbench_manager.create_workbench()
-
queued_filepath = entry.queued_media_file
queued_filename = workbench.localized_file(
mgg.queue_store, queued_filepath,
@@ -73,9 +75,8 @@ def process_video(entry):
thumbnail_filepath = create_pub_filepath(
entry, name_builder.fill('{basename}.thumbnail.jpg'))
- # Create a temporary file for the video destination
- tmp_dst = tempfile.NamedTemporaryFile()
-
+ # Create a temporary file for the video destination (cleaned up with workbench)
+ tmp_dst = NamedTemporaryFile(dir=workbench.dir, delete=False)
with tmp_dst:
# Transcode queued file to a VP8/vorbis file that fits in a 640x640 square
progress_callback = ProgressCallback(entry)
@@ -86,21 +87,20 @@ def process_video(entry):
vorbis_quality=video_config['vorbis_quality'],
progress_callback=progress_callback)
- # Push transcoded video to public storage
- _log.debug('Saving medium...')
- mgg.public_store.get_file(medium_filepath, 'wb').write(
- tmp_dst.read())
- _log.debug('Saved medium')
+ # Push transcoded video to public storage
+ _log.debug('Saving medium...')
+ mgg.public_store.copy_local_to_storage(tmp_dst.name, medium_filepath)
+ _log.debug('Saved medium')
- entry.media_files['webm_640'] = medium_filepath
+ entry.media_files['webm_640'] = medium_filepath
- # Save the width and height of the transcoded video
- entry.media_data_init(
- width=transcoder.dst_data.videowidth,
- height=transcoder.dst_data.videoheight)
+ # Save the width and height of the transcoded video
+ entry.media_data_init(
+ width=transcoder.dst_data.videowidth,
+ height=transcoder.dst_data.videoheight)
- # Create a temporary file for the video thumbnail
- tmp_thumb = tempfile.NamedTemporaryFile(suffix='.jpg')
+ # Temporary file for the video thumbnail (cleaned up with workbench)
+ tmp_thumb = NamedTemporaryFile(dir=workbench.dir, suffix='.jpg', delete=False)
with tmp_thumb:
# Create a thumbnail.jpg that fits in a 180x180 square
@@ -109,29 +109,16 @@ def process_video(entry):
tmp_thumb.name,
180)
- # Push the thumbnail to public storage
- _log.debug('Saving thumbnail...')
- mgg.public_store.get_file(thumbnail_filepath, 'wb').write(
- tmp_thumb.read())
- _log.debug('Saved thumbnail')
-
- entry.media_files['thumb'] = thumbnail_filepath
+ # Push the thumbnail to public storage
+ _log.debug('Saving thumbnail...')
+ mgg.public_store.copy_local_to_storage(tmp_thumb.name, thumbnail_filepath)
+ entry.media_files['thumb'] = thumbnail_filepath
if video_config['keep_original']:
# Push original file to public storage
- queued_file = file(queued_filename, 'rb')
-
- with queued_file:
- original_filepath = create_pub_filepath(
- entry,
- queued_filepath[-1])
-
- with mgg.public_store.get_file(original_filepath, 'wb') as \
- original_file:
- _log.debug('Saving original...')
- original_file.write(queued_file.read())
- _log.debug('Saved original')
-
- entry.media_files['original'] = original_filepath
+ _log.debug('Saving original...')
+ original_filepath = create_pub_filepath(entry, queued_filepath[-1])
+ mgg.public_store.copy_local_to_storage(queued_filename, original_filepath)
+ entry.media_files['original'] = original_filepath
mgg.queue_store.delete_file(queued_filepath)
diff --git a/mediagoblin/static/css/base.css b/mediagoblin/static/css/base.css
index 2085cfc9..04b4ee28 100644
--- a/mediagoblin/static/css/base.css
+++ b/mediagoblin/static/css/base.css
@@ -113,10 +113,12 @@ input, textarea {
header {
width: 100%;
+ max-width: 940px;
+ margin-left: auto;
+ margin-right: auto;
padding: 0;
margin-bottom: 42px;
- background-color: #303030;
- border-bottom: 1px solid #252525;
+ border-bottom: 1px solid #333;
}
.header_right {
@@ -125,19 +127,18 @@ header {
float: right;
}
-.header_right ul {
- display: none;
- position: absolute;
- top: 42px;
- right: 0px;
- background: #252525;
- padding: 20px;
+.header_dropdown {
+ margin-bottom: 20px;
}
-.header_right li {
+.header_dropdown li {
list-style: none;
}
+.dropdown_title {
+ font-size: 20px;
+}
+
a.logo {
color: #fff;
font-weight: bold;
@@ -145,7 +146,7 @@ a.logo {
.logo img {
vertical-align: middle;
- margin: 6px 8px;
+ margin: 6px 8px 6px 0;
}
.mediagoblin_content {
@@ -331,6 +332,12 @@ textarea#description, textarea#bio {
height: 100px;
}
+.delete {
+ margin-top: 36px;
+ display: block;
+ text-align: center;
+}
+
/* comments */
.comment_wrapper {
diff --git a/mediagoblin/static/js/header_dropdown.js b/mediagoblin/static/js/header_dropdown.js
new file mode 100644
index 00000000..1b2fb00f
--- /dev/null
+++ b/mediagoblin/static/js/header_dropdown.js
@@ -0,0 +1,27 @@
+/**
+ * GNU MediaGoblin -- federated, autonomous media hosting
+ * Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+ *
+ * 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/>.
+ */
+
+$(document).ready(function(){
+ $(".header_dropdown").hide();
+ $(".header_dropdown_up").hide();
+ $(".header_dropdown_down,.header_dropdown_up").click(function() {
+ $(".header_dropdown_down").toggle();
+ $(".header_dropdown_up").toggle();
+ $(".header_dropdown").slideToggle();
+ });
+});
diff --git a/mediagoblin/templates/mediagoblin/base.html b/mediagoblin/templates/mediagoblin/base.html
index 0a9a56d3..98b32f4a 100644
--- a/mediagoblin/templates/mediagoblin/base.html
+++ b/mediagoblin/templates/mediagoblin/base.html
@@ -28,6 +28,9 @@
<link rel="shortcut icon"
href="{{ request.staticdirect('/images/goblin.ico') }}" />
<script src="{{ request.staticdirect('/js/extlib/jquery.js') }}"></script>
+ <script type="text/javascript"
+ src="{{ request.staticdirect('/js/header_dropdown.js') }}"></script>
+
<!--[if lt IE 9]>
<script src="{{ request.staticdirect('/js/extlib/html5shiv.js') }}"></script>
<![endif]-->
@@ -45,21 +48,16 @@
{% block mediagoblin_header_title %}{% endblock %}
<div class="header_right">
{% if request.user %}
- {% trans
- user_url=request.urlgen('mediagoblin.user_pages.user_home',
- user= request.user.username),
- user_name=request.user.username -%}
- <a href="{{ user_url }}">{{ user_name }}</a>'s account
- {%- endtrans %}
- (<a href="{{ request.urlgen('mediagoblin.auth.logout') }}">{% trans %}log out{% endtrans %}</a>)
{% if request.user and request.user.status == 'active' %}
- <a class="button_action" href="{{ request.urlgen('mediagoblin.submit.start') }}">{% trans %}Add media{% endtrans %}</a>
+ <div class="button_action header_dropdown_down">&#9660;</div>
+ <div class="button_action header_dropdown_up">&#9650;</div>
{% elif request.user and request.user.status == "needs_email_verification" %}
{# the following link should only appear when verification is needed #}
<a href="{{ request.urlgen('mediagoblin.user_pages.user_home',
user=request.user.username) }}"
class="button_action_highlight">
{% trans %}Verify your email!{% endtrans %}</a>
+ or <a href="{{ request.urlgen('mediagoblin.auth.logout') }}">{% trans %}log out{% endtrans %}</a>
{% endif %}
{% else %}
<a href="{{ request.urlgen('mediagoblin.auth.login') }}?next={{
@@ -68,6 +66,44 @@
{% endif %}
</div>
<div class="clear"></div>
+ {% if request.user and request.user.status == 'active' %}
+ <div class="header_dropdown">
+ <p>
+ <span class="dropdown_title">
+ {% trans user_url=request.urlgen('mediagoblin.user_pages.user_home',
+ user=request.user.username),
+ user_name=request.user.username -%}
+ <a href="{{ user_url }}">{{ user_name }}</a>'s account
+ {%- endtrans %}
+ </span>
+ (<a href="{{ request.urlgen('mediagoblin.auth.logout') }}">{% trans %}log out{% endtrans %}</a>)
+ </p>
+ <ul>
+ <li><a class="button_action" href="{{ request.urlgen('mediagoblin.submit.start') }}">
+ {%- trans %}Add media{% endtrans -%}
+ </a></li>
+ <li><a class="button_action" href="{{ request.urlgen('mediagoblin.submit.collection') }}">
+ {%- trans %}Create new collection{% endtrans -%}
+ </a></li>
+ <li><a href="{{ request.urlgen('mediagoblin.edit.account') }}">
+ {%- trans %}Change account settings{% endtrans -%}
+ </a></li>
+ <li><a href="{{ request.urlgen('mediagoblin.user_pages.processing_panel',
+ user=request.user.username) }}">
+ {%- trans %}Media processing panel{% endtrans -%}
+ </a></li>
+ {% if request.user.is_admin %}
+ <li>Admin:
+ <ul>
+ <li><a href="{{ request.urlgen('mediagoblin.admin.panel') }}">
+ {%- trans %}Media processing panel{% endtrans -%}
+ </a></li>
+ </ul>
+ </li>
+ {% endif %}
+ </ul>
+ </div>
+ {% endif %}
</header>
{% endblock %}
<div class="container">
diff --git a/mediagoblin/templates/mediagoblin/edit/delete_account.html b/mediagoblin/templates/mediagoblin/edit/delete_account.html
new file mode 100644
index 00000000..84d0b580
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/edit/delete_account.html
@@ -0,0 +1,48 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# 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.delete_account') }}"
+ method="POST" enctype="multipart/form-data">
+ <div class="form_box">
+ <h1>
+ {%- trans user_name=user.username -%}
+ Really delete user '{{ user_name }}' and all related media/comments?
+ {%- endtrans -%}
+ </h1>
+ <p class="delete_checkbox_box">
+ <input type="checkbox" name="confirmed"/>
+ <label for="confirmed">
+ {%- trans %}Yes, really delete my account{% endtrans -%}
+ </label>
+ </p>
+
+ <div class="form_submit_buttons">
+ <a class="button_action" href="{{ request.urlgen(
+ 'mediagoblin.user_pages.user_home',
+ user=user.username) }}">{% trans %}Cancel{% endtrans %}</a>
+ {{ csrf_token }}
+ <input type="submit" value="{% trans %}Delete permanently{% endtrans %}" class="button_form" />
+ </div>
+ </div>
+ </form>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/edit/edit.html b/mediagoblin/templates/mediagoblin/edit/edit.html
index 1f5b91f7..9a040095 100644
--- a/mediagoblin/templates/mediagoblin/edit/edit.html
+++ b/mediagoblin/templates/mediagoblin/edit/edit.html
@@ -29,7 +29,7 @@
<form action="{{ request.urlgen('mediagoblin.edit.edit_media',
user= media.get_uploader.username,
- media= media.id) }}"
+ media_id=media.id) }}"
method="POST" enctype="multipart/form-data">
<div class="form_box_xl edit_box">
<h1>{% trans media_title=media.title %}Editing {{ media_title }}{% endtrans %}</h1>
diff --git a/mediagoblin/templates/mediagoblin/edit/edit_account.html b/mediagoblin/templates/mediagoblin/edit/edit_account.html
index 38d99893..c7a876e9 100644
--- a/mediagoblin/templates/mediagoblin/edit/edit_account.html
+++ b/mediagoblin/templates/mediagoblin/edit/edit_account.html
@@ -53,4 +53,9 @@
</div>
</div>
</form>
+ <div class="delete">
+ <a href="{{ request.urlgen('mediagoblin.edit.delete_account') }}">
+ {%- trans %}Delete my account{% endtrans -%}
+ </a>
+ </div>
{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/root.html b/mediagoblin/templates/mediagoblin/root.html
index 047dd2bb..5c6eb52f 100644
--- a/mediagoblin/templates/mediagoblin/root.html
+++ b/mediagoblin/templates/mediagoblin/root.html
@@ -21,33 +21,6 @@
{% block mediagoblin_content %}
{% if request.user %}
- {% if request.user.status == 'active' %}
- <h1>{% trans %}Actions{% endtrans %}</h1>
- <ul>
- <li><a href="{{ request.urlgen('mediagoblin.submit.start') }}">
- {%- trans %}Add media{% endtrans -%}
- </a></li>
- <li><a href="{{ request.urlgen('mediagoblin.submit.collection') }}">
- {%- trans %}Create new collection{% endtrans -%}
- </a></li>
- <li><a href="{{ request.urlgen('mediagoblin.edit.account') }}">
- {%- trans %}Change account settings{% endtrans -%}
- </a></li>
- <li><a href="{{ request.urlgen('mediagoblin.user_pages.processing_panel',
- user=request.user.username) }}">
- {%- trans %}Media processing panel{% endtrans -%}
- </a></li>
- {% if request.user.is_admin %}
- <li>Admin:
- <ul>
- <li><a href="{{ request.urlgen('mediagoblin.admin.panel') }}">
- {%- trans %}Media processing panel{% endtrans -%}
- </a></li>
- </ul>
- </li>
- {% endif %}
- </ul>
- {% endif %}
<h1>{% trans %}Explore{% endtrans %}</h1>
{% else %}
<h1>{% trans %}Hi there, welcome to this MediaGoblin site!{% endtrans %}</h1>
diff --git a/mediagoblin/templates/mediagoblin/user_pages/collection.html b/mediagoblin/templates/mediagoblin/user_pages/collection.html
index f1ab7a42..5a7baadd 100644
--- a/mediagoblin/templates/mediagoblin/user_pages/collection.html
+++ b/mediagoblin/templates/mediagoblin/user_pages/collection.html
@@ -57,7 +57,9 @@
{% endif %}
<p>
- {{ collection.description }}
+ {% autoescape False %}
+ {{ collection.description_html }}
+ {% endautoescape %}
</p>
{{ collection_gallery(request, collection_items, pagination) }}
diff --git a/mediagoblin/templates/mediagoblin/user_pages/collection_list.html b/mediagoblin/templates/mediagoblin/user_pages/collection_list.html
new file mode 100644
index 00000000..abf22623
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/user_pages/collection_list.html
@@ -0,0 +1,56 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# 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" %}
+
+{% block title %}
+ {%- trans username=user.username -%}
+ {{ username }}'s collections
+ {%- endtrans %} &mdash; {{ super() }}
+{% endblock %}
+
+{% block mediagoblin_content -%}
+ <h1>
+ {%- trans username=user.username,
+ user_url=request.urlgen(
+ 'mediagoblin.user_pages.user_home',
+ user=user.username) -%}
+ <a href="{{ user_url }}">{{ username }}</a>'s collections
+ {%- endtrans %}
+ </h1>
+
+ {% if request.user %}
+ {% if request.user.status == 'active' %}
+ <p>
+ <a href="{{ request.urlgen('mediagoblin.submit.collection',
+ user=user.username) }}">
+ {%- trans %}Create new collection{% endtrans -%}
+ </p>
+ {% endif %}
+ {% endif %}
+
+ <ul>
+ {% for coll in collections %}
+ {% set coll_url = request.urlgen(
+ 'mediagoblin.user_pages.user_collection',
+ user=user.username,
+ collection=coll.slug) %}
+ <li><a href="{{ coll_url }}">{{ coll.title }}</li>
+ {% endfor %}
+ </ul>
+
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html
index 11f2a2a1..29d7874c 100644
--- a/mediagoblin/templates/mediagoblin/user_pages/media.html
+++ b/mediagoblin/templates/mediagoblin/user_pages/media.html
@@ -83,11 +83,11 @@
request.user.is_admin) %}
{% set edit_url = request.urlgen('mediagoblin.edit.edit_media',
user= media.get_uploader.username,
- media= media.id) %}
+ media_id=media.id) %}
<a class="button_action" href="{{ edit_url }}">{% trans %}Edit{% endtrans %}</a>
{% set delete_url = request.urlgen('mediagoblin.user_pages.media_confirm_delete',
user= media.get_uploader.username,
- media= media.id) %}
+ media_id=media.id) %}
<a class="button_action" href="{{ delete_url }}">{% trans %}Delete{% endtrans %}</a>
{% endif %}
{% autoescape False %}
@@ -104,7 +104,7 @@
{% if request.user %}
<form action="{{ request.urlgen('mediagoblin.user_pages.media_post_comment',
user= media.get_uploader.username,
- media=media.id) }}" method="POST" id="form_comment">
+ media_id=media.id) }}" method="POST" id="form_comment">
<p>
{% trans %}You can use <a href="http://daringfireball.net/projects/markdown/basics">Markdown</a> for formatting.{% endtrans %}
</p>
@@ -115,35 +115,38 @@
</div>
</form>
{% endif %}
+ <ul style="list-style:none">
{% for comment in comments %}
{% set comment_author = comment.get_author %}
- {% if pagination.active_id == comment.id %}
- <div class="comment_wrapper comment_active" id="comment-{{ comment.id }}">
- <a name="comment" id="comment"></a>
- {% else %}
- <div class="comment_wrapper" id="comment-{{ comment.id }}">
- {% endif %}
- <div class="comment_author">
+ <li id="comment-{{ comment.id }}"
+ {%- if pagination.active_id == comment.id %}
+ class="comment_wrapper comment_active">
+ <a name="comment" id="comment"></a>
+ {%- else %}
+ class="comment_wrapper">
+ {%- endif %}
+ <div class="comment_author">
<img src="{{ request.staticdirect('/images/icon_comment.png') }}" />
<a href="{{ request.urlgen('mediagoblin.user_pages.user_home',
user = comment_author.username) }}">
- {{ comment_author.username -}}
+ {{- comment_author.username -}}
</a>
{% trans %}at{% endtrans %}
<a href="{{ request.urlgen('mediagoblin.user_pages.media_home.view_comment',
comment = comment.id,
user = media.get_uploader.username,
media = media.slug_or_id) }}#comment">
- {{ comment.created.strftime("%I:%M%p %Y-%m-%d") }}
+ {{- comment.created.strftime("%I:%M%p %Y-%m-%d") -}}
</a>:
</div>
<div class="comment_content">
- {% autoescape False %}
+ {% autoescape False -%}
{{ comment.content_html }}
- {% endautoescape %}
+ {%- endautoescape %}
</div>
- </div>
+ </li>
{% endfor %}
+ </ul>
{{ render_pagination(request, pagination,
media.url_for_self(request.urlgen)) }}
{% endif %}
diff --git a/mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html b/mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html
index 833f500d..d2a5655e 100644
--- a/mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html
+++ b/mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html
@@ -23,7 +23,7 @@
<form action="{{ request.urlgen('mediagoblin.user_pages.media_confirm_delete',
user=media.get_uploader.username,
- media=media.id) }}"
+ media_id=media.id) }}"
method="POST" enctype="multipart/form-data">
<div class="form_box">
<h1>
diff --git a/mediagoblin/templates/mediagoblin/user_pages/user.html b/mediagoblin/templates/mediagoblin/user_pages/user.html
index 76bce1e2..71acd66c 100644
--- a/mediagoblin/templates/mediagoblin/user_pages/user.html
+++ b/mediagoblin/templates/mediagoblin/user_pages/user.html
@@ -118,6 +118,12 @@
</a>
{% endif %}
{% endif %}
+ <p>
+ <a href="{{ request.urlgen('mediagoblin.user_pages.collection_list',
+ user=user.username) }}">
+ {%- trans %}Browse collections{% endtrans -%}
+ </a>
+ </p>
</div>
{% if media_entries.count() %}
diff --git a/mediagoblin/tests/test_api.py b/mediagoblin/tests/test_api.py
index 4b784da3..82b1c1b4 100644
--- a/mediagoblin/tests/test_api.py
+++ b/mediagoblin/tests/test_api.py
@@ -22,7 +22,7 @@ from pkg_resources import resource_filename
from mediagoblin import mg_globals
from mediagoblin.tools import template, pluginapi
-from mediagoblin.tests.tools import get_test_app, fixture_add_user
+from mediagoblin.tests.tools import get_app, fixture_add_user
_log = logging.getLogger(__name__)
@@ -44,7 +44,7 @@ BIG_BLUE = resource('bigblue.png')
class TestAPI(object):
def setUp(self):
- self.app = get_test_app(dump_old_app=False)
+ self.app = get_app(dump_old_app=False)
self.db = mg_globals.database
self.user_password = u'4cc355_70k3N'
diff --git a/mediagoblin/tests/test_auth.py b/mediagoblin/tests/test_auth.py
index a40c9cbc..103bea6b 100644
--- a/mediagoblin/tests/test_auth.py
+++ b/mediagoblin/tests/test_auth.py
@@ -22,7 +22,7 @@ from nose.tools import assert_equal
from mediagoblin import mg_globals
from mediagoblin.auth import lib as auth_lib
from mediagoblin.db.models import User
-from mediagoblin.tests.tools import get_test_app, fixture_add_user
+from mediagoblin.tests.tools import setup_fresh_app, get_app, fixture_add_user
from mediagoblin.tools import template, mail
@@ -67,11 +67,11 @@ def test_bcrypt_gen_password_hash():
'notthepassword', hashed_pw, '3><7R45417')
-def test_register_views():
+@setup_fresh_app
+def test_register_views(test_app):
"""
Massive test function that all our registration-related views all work.
"""
- test_app = get_test_app(dump_old_app=False)
# Test doing a simple GET on the page
# -----------------------------------
@@ -125,7 +125,7 @@ def test_register_views():
u'Invalid email address.']
## At this point there should be no users in the database ;)
- assert not User.query.count()
+ assert_equal(User.query.count(), 0)
# Successful register
# -------------------
@@ -315,7 +315,7 @@ def test_authentication_views():
"""
Test logging in and logging out
"""
- test_app = get_test_app(dump_old_app=False)
+ test_app = get_app(dump_old_app=False)
# Make a new user
test_user = fixture_add_user(active_user=False)
diff --git a/mediagoblin/tests/test_collections.py b/mediagoblin/tests/test_collections.py
new file mode 100644
index 00000000..b19f6362
--- /dev/null
+++ b/mediagoblin/tests/test_collections.py
@@ -0,0 +1,37 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS.
+#
+# 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/>.
+
+from mediagoblin.tests.tools import fixture_add_collection, fixture_add_user, \
+ get_app
+from mediagoblin.db.models import Collection, User
+from mediagoblin.db.base import Session
+from nose.tools import assert_equal
+
+
+def test_user_deletes_collection():
+ # Setup db.
+ get_app(dump_old_app=False)
+
+ user = fixture_add_user()
+ coll = fixture_add_collection(user=user)
+ # Reload into session:
+ user = User.query.get(user.id)
+
+ cnt1 = Collection.query.count()
+ user.delete()
+ cnt2 = Collection.query.count()
+
+ assert_equal(cnt1, cnt2 + 1)
diff --git a/mediagoblin/tests/test_csrf_middleware.py b/mediagoblin/tests/test_csrf_middleware.py
index 22a0eb04..e720264c 100644
--- a/mediagoblin/tests/test_csrf_middleware.py
+++ b/mediagoblin/tests/test_csrf_middleware.py
@@ -14,12 +14,12 @@
# 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.tests.tools import get_test_app
+from mediagoblin.tests.tools import get_app
from mediagoblin import mg_globals
def test_csrf_cookie_set():
- test_app = get_test_app(dump_old_app=False)
+ test_app = get_app(dump_old_app=False)
cookie_name = mg_globals.app_config['csrf_cookie_name']
# get login page
@@ -37,7 +37,7 @@ def test_csrf_token_must_match():
# We need a fresh app for this test on webtest < 1.3.6.
# We do not understand why, but it fixes the tests.
# If we require webtest >= 1.3.6, we can switch to a non fresh app here.
- test_app = get_test_app(dump_old_app=True)
+ test_app = get_app(dump_old_app=True)
# construct a request with no cookie or form token
assert test_app.post('/auth/login/',
@@ -68,7 +68,7 @@ def test_csrf_token_must_match():
status_int == 200
def test_csrf_exempt():
- test_app = get_test_app(dump_old_app=False)
+ test_app = get_app(dump_old_app=False)
# monkey with the views to decorate a known endpoint
import mediagoblin.auth.views
from mediagoblin.meddleware.csrf import csrf_exempt
diff --git a/mediagoblin/tests/test_edit.py b/mediagoblin/tests/test_edit.py
index cbdad649..7db6eaea 100644
--- a/mediagoblin/tests/test_edit.py
+++ b/mediagoblin/tests/test_edit.py
@@ -18,13 +18,13 @@ from nose.tools import assert_equal
from mediagoblin import mg_globals
from mediagoblin.db.models import User
-from mediagoblin.tests.tools import get_test_app, fixture_add_user
+from mediagoblin.tests.tools import get_app, fixture_add_user
from mediagoblin.tools import template
from mediagoblin.auth.lib import bcrypt_check_password
class TestUserEdit(object):
def setUp(self):
- self.app = get_test_app(dump_old_app=False)
+ self.app = get_app(dump_old_app=False)
# set up new user
self.user_password = u'toast'
self.user = fixture_add_user(password = self.user_password)
@@ -37,6 +37,24 @@ class TestUserEdit(object):
'password': self.user_password})
+ def test_user_deletion(self):
+ """Delete user via web interface"""
+ # Make sure user exists
+ assert User.query.filter_by(username=u'chris').first()
+
+ res = self.app.post('/edit/account/delete/', {'confirmed': 'y'})
+
+ # Make sure user has been deleted
+ assert User.query.filter_by(username=u'chris').first() == None
+
+ #TODO: make sure all corresponding items comments etc have been
+ # deleted too. Perhaps in submission test?
+
+ #Restore user at end of test
+ self.user = fixture_add_user(password = self.user_password)
+ self.login()
+
+
def test_change_password(self):
"""Test changing password correctly and incorrectly"""
# test that the password can be changed
diff --git a/mediagoblin/tests/test_http_callback.py b/mediagoblin/tests/test_http_callback.py
index 0f6e489f..8bee7045 100644
--- a/mediagoblin/tests/test_http_callback.py
+++ b/mediagoblin/tests/test_http_callback.py
@@ -20,14 +20,14 @@ from urlparse import urlparse, parse_qs
from mediagoblin import mg_globals
from mediagoblin.tools import processing
-from mediagoblin.tests.tools import get_test_app, fixture_add_user
+from mediagoblin.tests.tools import get_app, fixture_add_user
from mediagoblin.tests.test_submission import GOOD_PNG
from mediagoblin.tests import test_oauth as oauth
class TestHTTPCallback(object):
def setUp(self):
- self.app = get_test_app(dump_old_app=False)
+ self.app = get_app(dump_old_app=False)
self.db = mg_globals.database
self.user_password = u'secret'
diff --git a/mediagoblin/tests/test_messages.py b/mediagoblin/tests/test_messages.py
index c587e599..4c0f3e2e 100644
--- a/mediagoblin/tests/test_messages.py
+++ b/mediagoblin/tests/test_messages.py
@@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from mediagoblin.messages import fetch_messages, add_message
-from mediagoblin.tests.tools import get_test_app
+from mediagoblin.tests.tools import get_app
from mediagoblin.tools import template
@@ -26,7 +26,7 @@ def test_messages():
fetched messages should be the same as the added ones,
and fetching should clear the message list.
"""
- test_app = get_test_app(dump_old_app=False)
+ test_app = get_app(dump_old_app=False)
# Aquire a request object
test_app.get('/')
context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
diff --git a/mediagoblin/tests/test_misc.py b/mediagoblin/tests/test_misc.py
index 8a96e7d0..ae5d7e50 100644
--- a/mediagoblin/tests/test_misc.py
+++ b/mediagoblin/tests/test_misc.py
@@ -16,9 +16,9 @@
from nose.tools import assert_equal
-from mediagoblin.tests.tools import get_test_app
+from mediagoblin.tests.tools import get_app
def test_404_for_non_existent():
- test_app = get_test_app(dump_old_app=False)
+ test_app = get_app(dump_old_app=False)
res = test_app.get('/does-not-exist/', expect_errors=True)
assert_equal(res.status_int, 404)
diff --git a/mediagoblin/tests/test_oauth.py b/mediagoblin/tests/test_oauth.py
index a72f766e..94ba5dab 100644
--- a/mediagoblin/tests/test_oauth.py
+++ b/mediagoblin/tests/test_oauth.py
@@ -21,7 +21,7 @@ from urlparse import parse_qs, urlparse
from mediagoblin import mg_globals
from mediagoblin.tools import template, pluginapi
-from mediagoblin.tests.tools import get_test_app, fixture_add_user
+from mediagoblin.tests.tools import get_app, fixture_add_user
_log = logging.getLogger(__name__)
@@ -29,7 +29,7 @@ _log = logging.getLogger(__name__)
class TestOAuth(object):
def setUp(self):
- self.app = get_test_app()
+ self.app = get_app()
self.db = mg_globals.database
self.pman = pluginapi.PluginManager()
diff --git a/mediagoblin/tests/test_submission.py b/mediagoblin/tests/test_submission.py
index faf4e744..00f1ed3d 100644
--- a/mediagoblin/tests/test_submission.py
+++ b/mediagoblin/tests/test_submission.py
@@ -24,7 +24,7 @@ import os
from nose.tools import assert_equal, assert_true
from pkg_resources import resource_filename
-from mediagoblin.tests.tools import get_test_app, \
+from mediagoblin.tests.tools import get_app, \
fixture_add_user
from mediagoblin import mg_globals
from mediagoblin.tools import template
@@ -50,7 +50,7 @@ REQUEST_CONTEXT = ['mediagoblin/user_pages/user.html', 'request']
class TestSubmission:
def setUp(self):
- self.test_app = get_test_app(dump_old_app=False)
+ self.test_app = get_app(dump_old_app=False)
# TODO: Possibly abstract into a decorator like:
# @as_authenticated_user('chris')
@@ -161,11 +161,23 @@ class TestSubmission:
media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
media_id = media.id
+ # render and post to the edit page.
+ edit_url = request.urlgen(
+ 'mediagoblin.edit.edit_media',
+ user=self.test_user.username, media_id=media_id)
+ self.test_app.get(edit_url)
+ self.test_app.post(edit_url,
+ {'title': u'Balanced Goblin',
+ 'slug': u"Balanced=Goblin",
+ 'tags': u''})
+ media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
+ assert_equal(media.slug, u"balanced-goblin")
+
# Add a comment, so we can test for its deletion later.
self.check_comments(request, media_id, 0)
comment_url = request.urlgen(
'mediagoblin.user_pages.media_post_comment',
- user=self.test_user.username, media=media_id)
+ user=self.test_user.username, media_id=media_id)
response = self.do_post({'comment_content': 'i love this test'},
url=comment_url, do_follow=True)[0]
self.check_comments(request, media_id, 1)
@@ -174,7 +186,7 @@ class TestSubmission:
# ---------------------------------------------------
delete_url = request.urlgen(
'mediagoblin.user_pages.media_confirm_delete',
- user=self.test_user.username, media=media_id)
+ user=self.test_user.username, media_id=media_id)
# Empty data means don't confirm
response = self.do_post({}, do_follow=True, url=delete_url)[0]
media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
diff --git a/mediagoblin/tests/test_tags.py b/mediagoblin/tests/test_tags.py
index 73af2eea..ccb93085 100644
--- a/mediagoblin/tests/test_tags.py
+++ b/mediagoblin/tests/test_tags.py
@@ -14,7 +14,7 @@
# 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.tests.tools import get_test_app
+from mediagoblin.tests.tools import get_app
from mediagoblin.tools import text
def test_list_of_dicts_conversion():
@@ -24,7 +24,7 @@ def test_list_of_dicts_conversion():
as a dict. Each tag dict should contain the tag's name and slug. Another
function performs the reverse operation when populating a form to edit tags.
"""
- test_app = get_test_app(dump_old_app=False)
+ test_app = get_app(dump_old_app=False)
# Leading, trailing, and internal whitespace should be removed and slugified
assert text.convert_to_tag_list_of_dicts('sleep , 6 AM, chainsaw! ') == [
{'name': u'sleep', 'slug': u'sleep'},
diff --git a/mediagoblin/tests/test_tests.py b/mediagoblin/tests/test_tests.py
index d09e8f28..d539f1e0 100644
--- a/mediagoblin/tests/test_tests.py
+++ b/mediagoblin/tests/test_tests.py
@@ -15,22 +15,22 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from mediagoblin import mg_globals
-from mediagoblin.tests.tools import get_test_app, fixture_add_user
+from mediagoblin.tests.tools import get_app, fixture_add_user
from mediagoblin.db.models import User
-def test_get_test_app_wipes_db():
+def test_get_app_wipes_db():
"""
Make sure we get a fresh database on every wipe :)
"""
- get_test_app(dump_old_app=True)
+ get_app(dump_old_app=True)
assert User.query.count() == 0
fixture_add_user()
assert User.query.count() == 1
- get_test_app(dump_old_app=False)
+ get_app(dump_old_app=False)
assert User.query.count() == 1
- get_test_app(dump_old_app=True)
+ get_app(dump_old_app=True)
assert User.query.count() == 0
diff --git a/mediagoblin/tests/test_workbench.py b/mediagoblin/tests/test_workbench.py
index 04a74653..636c8689 100644
--- a/mediagoblin/tests/test_workbench.py
+++ b/mediagoblin/tests/test_workbench.py
@@ -18,7 +18,9 @@ import os
import tempfile
-from mediagoblin import workbench
+from mediagoblin.tools import workbench
+from mediagoblin.mg_globals import setup_globals
+from mediagoblin.decorators import get_workbench
from mediagoblin.tests.test_storage import get_tmp_filestorage
@@ -28,19 +30,20 @@ class TestWorkbench(object):
os.path.join(tempfile.gettempdir(), u'mgoblin_workbench_testing'))
def test_create_workbench(self):
- workbench = self.workbench_manager.create_workbench()
+ workbench = self.workbench_manager.create()
assert os.path.isdir(workbench.dir)
assert workbench.dir.startswith(self.workbench_manager.base_workbench_dir)
+ workbench.destroy()
def test_joinpath(self):
- this_workbench = self.workbench_manager.create_workbench()
+ this_workbench = self.workbench_manager.create()
tmpname = this_workbench.joinpath('temp.txt')
assert tmpname == os.path.join(this_workbench.dir, 'temp.txt')
- this_workbench.destroy_self()
+ this_workbench.destroy()
def test_destroy_workbench(self):
# kill a workbench
- this_workbench = self.workbench_manager.create_workbench()
+ this_workbench = self.workbench_manager.create()
tmpfile_name = this_workbench.joinpath('temp.txt')
tmpfile = file(tmpfile_name, 'w')
with tmpfile:
@@ -49,14 +52,14 @@ class TestWorkbench(object):
assert os.path.exists(tmpfile_name)
wb_dir = this_workbench.dir
- this_workbench.destroy_self()
+ this_workbench.destroy()
assert not os.path.exists(tmpfile_name)
assert not os.path.exists(wb_dir)
def test_localized_file(self):
tmpdir, this_storage = get_tmp_filestorage()
- this_workbench = self.workbench_manager.create_workbench()
-
+ this_workbench = self.workbench_manager.create()
+
# Write a brand new file
filepath = ['dir1', 'dir2', 'ourfile.txt']
@@ -78,7 +81,7 @@ class TestWorkbench(object):
filename = this_workbench.localized_file(this_storage, filepath)
assert filename == os.path.join(
this_workbench.dir, 'ourfile.txt')
-
+
# fake remote file storage, filename_if_copying set
filename = this_workbench.localized_file(
this_storage, filepath, 'thisfile')
@@ -91,3 +94,18 @@ class TestWorkbench(object):
this_storage, filepath, 'thisfile.text', False)
assert filename == os.path.join(
this_workbench.dir, 'thisfile.text')
+
+ def test_workbench_decorator(self):
+ """Test @get_workbench decorator and automatic cleanup"""
+ # The decorator needs mg_globals.workbench_manager
+ setup_globals(workbench_manager=self.workbench_manager)
+
+ @get_workbench
+ def create_it(workbench=None):
+ # workbench dir exists?
+ assert os.path.isdir(workbench.dir)
+ return workbench.dir
+
+ benchdir = create_it()
+ # workbench dir has been cleaned up automatically?
+ assert not os.path.isdir(benchdir)
diff --git a/mediagoblin/tests/tools.py b/mediagoblin/tests/tools.py
index 3e78b2e3..18d4ec0c 100644
--- a/mediagoblin/tests/tools.py
+++ b/mediagoblin/tests/tools.py
@@ -25,7 +25,7 @@ from paste.deploy import loadapp
from webtest import TestApp
from mediagoblin import mg_globals
-from mediagoblin.db.models import User
+from mediagoblin.db.models import User, Collection
from mediagoblin.tools import testing
from mediagoblin.init.config import read_mediagoblin_config
from mediagoblin.db.open import setup_connection_and_db_from_config
@@ -103,7 +103,7 @@ def suicide_if_bad_celery_environ():
raise BadCeleryEnviron(BAD_CELERY_MESSAGE)
-def get_test_app(dump_old_app=True):
+def get_app(dump_old_app=True):
suicide_if_bad_celery_environ()
# Make sure we've turned on testing
@@ -164,7 +164,7 @@ def setup_fresh_app(func):
"""
@wraps(func)
def wrapper(*args, **kwargs):
- test_app = get_test_app()
+ test_app = get_app()
testing.clear_test_buckets()
return func(test_app, *args, **kwargs)
@@ -226,3 +226,24 @@ def fixture_add_user(username=u'chris', password=u'toast',
Session.expunge(test_user)
return test_user
+
+
+def fixture_add_collection(name=u"My first Collection", user=None):
+ if user is None:
+ user = fixture_add_user()
+ coll = Collection.query.filter_by(creator=user.id, title=name).first()
+ if coll is not None:
+ return coll
+ coll = Collection()
+ coll.creator = user.id
+ coll.title = name
+ coll.generate_slug()
+ coll.save()
+
+ # Reload
+ Session.refresh(coll)
+
+ # ... and detach from session:
+ Session.expunge(coll)
+
+ return coll
diff --git a/mediagoblin/workbench.py b/mediagoblin/tools/workbench.py
index 2331b551..0bd4096b 100644
--- a/mediagoblin/workbench.py
+++ b/mediagoblin/tools/workbench.py
@@ -19,10 +19,6 @@ import shutil
import tempfile
-DEFAULT_WORKBENCH_DIR = os.path.join(
- tempfile.gettempdir(), u'mgoblin_workbench')
-
-
# Actual workbench stuff
# ----------------------
@@ -119,7 +115,7 @@ class Workbench(object):
return full_dest_filename
- def destroy_self(self):
+ def destroy(self):
"""
Destroy this workbench! Deletes the directory and all its contents!
@@ -127,18 +123,33 @@ class Workbench(object):
"""
# just in case
workbench = os.path.abspath(self.dir)
-
shutil.rmtree(workbench)
-
del self.dir
+ def __enter__(self):
+ """Make Workbench a context manager so we can use `with Workbench() as bench:`"""
+ return self
+
+ def __exit__(self, *args):
+ """Clean up context manager, aka ourselves, deleting the workbench"""
+ self.destroy()
+
class WorkbenchManager(object):
"""
A system for generating and destroying workbenches.
- Workbenches are actually just subdirectories of a temporary storage space
- for during the processing stage.
+ Workbenches are actually just subdirectories of a (local) temporary
+ storage space for during the processing stage. The preferred way to
+ create them is to use:
+
+ with workbenchmger.create() as workbench:
+ do stuff...
+
+ This will automatically clean up all temporary directories even in
+ case of an exceptions. Also check the
+ @mediagoblin.decorators.get_workbench decorator for a convenient
+ wrapper.
"""
def __init__(self, base_workbench_dir):
@@ -146,7 +157,7 @@ class WorkbenchManager(object):
if not os.path.exists(self.base_workbench_dir):
os.makedirs(self.base_workbench_dir)
- def create_workbench(self):
+ def create(self):
"""
Create and return the path to a new workbench (directory).
"""
diff --git a/mediagoblin/user_pages/routing.py b/mediagoblin/user_pages/routing.py
index 63bf5c2a..2b228355 100644
--- a/mediagoblin/user_pages/routing.py
+++ b/mediagoblin/user_pages/routing.py
@@ -24,12 +24,12 @@ add_route('mediagoblin.user_pages.media_home',
'mediagoblin.user_pages.views:media_home')
add_route('mediagoblin.user_pages.media_confirm_delete',
- '/u/<string:user>/m/<string:media>/confirm-delete/',
+ '/u/<string:user>/m/<int:media_id>/confirm-delete/',
'mediagoblin.user_pages.views:media_confirm_delete')
# Submission handling of new comments. TODO: only allow for POST methods
add_route('mediagoblin.user_pages.media_post_comment',
- '/u/<string:user>/m/<string:media>/comment/add/',
+ '/u/<string:user>/m/<int:media_id>/comment/add/',
'mediagoblin.user_pages.views:media_post_comment')
add_route('mediagoblin.user_pages.user_gallery',
@@ -37,7 +37,7 @@ add_route('mediagoblin.user_pages.user_gallery',
'mediagoblin.user_pages.views:user_gallery')
add_route('mediagoblin.user_pages.media_home.view_comment',
- '/u/<string:user>/m/<string:media>/c/<string:comment>/',
+ '/u/<string:user>/m/<string:media>/c/<int:comment>/',
'mediagoblin.user_pages.views:media_home')
add_route('mediagoblin.user_pages.atom_feed',
@@ -48,6 +48,10 @@ add_route('mediagoblin.user_pages.media_collect',
'/u/<string:user>/m/<string:media>/collect/',
'mediagoblin.user_pages.views:media_collect')
+add_route('mediagoblin.user_pages.collection_list',
+ '/u/<string:user>/collections/',
+ 'mediagoblin.user_pages.views:collection_list')
+
add_route('mediagoblin.user_pages.user_collection',
'/u/<string:user>/collection/<string:collection>/',
'mediagoblin.user_pages.views:user_collection')
@@ -74,7 +78,7 @@ add_route('mediagoblin.user_pages.processing_panel',
# Stray edit routes
add_route('mediagoblin.edit.edit_media',
- '/u/<string:user>/m/<string:media>/edit/',
+ '/u/<string:user>/m/<int:media_id>/edit/',
'mediagoblin.edit.views:edit_media')
add_route('mediagoblin.edit.attachments',
diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py
index f115c3b8..dea47fbf 100644
--- a/mediagoblin/user_pages/views.py
+++ b/mediagoblin/user_pages/views.py
@@ -23,11 +23,11 @@ from mediagoblin.db.models import (MediaEntry, Collection, CollectionItem,
from mediagoblin.tools.response import render_to_response, render_404, redirect
from mediagoblin.tools.translate import pass_to_ugettext as _
from mediagoblin.tools.pagination import Pagination
-from mediagoblin.tools.files import delete_media_files
from mediagoblin.user_pages import forms as user_forms
from mediagoblin.user_pages.lib import send_comment_email
from mediagoblin.decorators import (uses_pagination, get_user_media_entry,
+ get_media_entry_by_id,
require_active_login, user_may_delete_media, user_may_alter_collection,
get_user_collection, get_user_collection_item, active_user_from_url)
@@ -110,12 +110,13 @@ def media_home(request, media, page, **kwargs):
"""
'Homepage' of a MediaEntry()
"""
- if request.matchdict.get('comment', None):
+ comment_id = request.matchdict.get('comment', None)
+ if comment_id:
pagination = Pagination(
page, media.get_comments(
mg_globals.app_config['comments_ascending']),
MEDIA_COMMENTS_PER_PAGE,
- request.matchdict.get('comment'))
+ comment_id)
else:
pagination = Pagination(
page, media.get_comments(
@@ -138,7 +139,7 @@ def media_home(request, media, page, **kwargs):
'app_config': mg_globals.app_config})
-@get_user_media_entry
+@get_media_entry_by_id
@require_active_login
def media_post_comment(request, media):
"""
@@ -226,6 +227,10 @@ def media_collect(request, media):
messages.add_message(
request, messages.ERROR,
_('You have to select or add a collection'))
+ return redirect(request, "mediagoblin.user_pages.media_collect",
+ user=media.get_uploader.username,
+ media=media.id)
+
# Check whether media already exists in collection
elif CollectionItem.query.filter_by(
@@ -258,7 +263,7 @@ def media_collect(request, media):
#TODO: Why does @user_may_delete_media not implicate @require_active_login?
-@get_user_media_entry
+@get_media_entry_by_id
@require_active_login
@user_may_delete_media
def media_confirm_delete(request, media):
@@ -268,21 +273,7 @@ def media_confirm_delete(request, media):
if request.method == 'POST' and form.validate():
if form.confirm.data is True:
username = media.get_uploader.username
-
- # Delete all the associated comments
- for comment in media.get_comments():
- comment.delete()
-
- # Delete all files on the public storage
- try:
- delete_media_files(media)
- except OSError, error:
- _log.error('No such files from the user "{1}"'
- ' to delete: {0}'.format(str(error), username))
- messages.add_message(request, messages.ERROR,
- _('Some of the files with this entry seem'
- ' to be missing. Deleting anyway.'))
-
+ # Delete MediaEntry and all related files, comments etc.
media.delete()
messages.add_message(
request, messages.SUCCESS, _('You deleted the media.'))
@@ -337,6 +328,19 @@ def user_collection(request, page, url_user=None):
'pagination': pagination})
+@active_user_from_url
+def collection_list(request, url_user=None):
+ """A User-defined Collection"""
+ collections = Collection.query.filter_by(
+ get_creator=url_user)
+
+ return render_to_response(
+ request,
+ 'mediagoblin/user_pages/collection_list.html',
+ {'user': url_user,
+ 'collections': collections})
+
+
@get_user_collection_item
@require_active_login
@user_may_alter_collection