diff options
Diffstat (limited to 'mediagoblin/submit')
-rw-r--r-- | mediagoblin/submit/__init__.py | 15 | ||||
-rw-r--r-- | mediagoblin/submit/forms.py | 53 | ||||
-rw-r--r-- | mediagoblin/submit/lib.py | 102 | ||||
-rw-r--r-- | mediagoblin/submit/routing.py | 21 | ||||
-rw-r--r-- | mediagoblin/submit/views.py | 153 |
5 files changed, 344 insertions, 0 deletions
diff --git a/mediagoblin/submit/__init__.py b/mediagoblin/submit/__init__.py new file mode 100644 index 00000000..621845ba --- /dev/null +++ b/mediagoblin/submit/__init__.py @@ -0,0 +1,15 @@ +# 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/>. diff --git a/mediagoblin/submit/forms.py b/mediagoblin/submit/forms.py new file mode 100644 index 00000000..e9bd93fd --- /dev/null +++ b/mediagoblin/submit/forms.py @@ -0,0 +1,53 @@ +# 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/>. + + +import wtforms + +from mediagoblin.tools.text import tag_length_validator +from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ +from mediagoblin.tools.licenses import licenses_as_choices + + +class SubmitStartForm(wtforms.Form): + file = wtforms.FileField(_('File')) + title = wtforms.TextField( + _('Title'), + [wtforms.validators.Length(min=0, max=500)]) + description = wtforms.TextAreaField( + _('Description of this work'), + description=_("""You can use + <a href="http://daringfireball.net/projects/markdown/basics"> + Markdown</a> for formatting.""")) + tags = wtforms.TextField( + _('Tags'), + [tag_length_validator], + description=_( + "Separate tags by commas.")) + license = wtforms.SelectField( + _('License'), + [wtforms.validators.Optional(),], + choices=licenses_as_choices()) + +class AddCollectionForm(wtforms.Form): + title = wtforms.TextField( + _('Title'), + [wtforms.validators.Length(min=0, max=500), wtforms.validators.Required()]) + description = wtforms.TextAreaField( + _('Description of this collection'), + description=_("""You can use + <a href="http://daringfireball.net/projects/markdown/basics"> + Markdown</a> for formatting.""")) diff --git a/mediagoblin/submit/lib.py b/mediagoblin/submit/lib.py new file mode 100644 index 00000000..7e85696b --- /dev/null +++ b/mediagoblin/submit/lib.py @@ -0,0 +1,102 @@ +# 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/>. + +import logging +import uuid +from werkzeug.utils import secure_filename +from werkzeug.datastructures import FileStorage + +from mediagoblin.db.models import MediaEntry +from mediagoblin.processing import mark_entry_failed +from mediagoblin.processing.task import process_media + + +_log = logging.getLogger(__name__) + + +def check_file_field(request, field_name): + """Check if a file field meets minimal criteria""" + retval = (field_name in request.files + and isinstance(request.files[field_name], FileStorage) + and request.files[field_name].stream) + if not retval: + _log.debug("Form did not contain proper file field %s", field_name) + return retval + + +def new_upload_entry(user): + """ + Create a new MediaEntry for uploading + """ + entry = MediaEntry() + entry.uploader = user.id + entry.license = user.license_preference + return entry + + +def prepare_queue_task(app, entry, filename): + """ + Prepare a MediaEntry for the processing queue and get a queue file + """ + # We generate this ourselves so we know what the task id is for + # retrieval later. + + # (If we got it off the task's auto-generation, there'd be + # a risk of a race condition when we'd save after sending + # off the task) + task_id = unicode(uuid.uuid4()) + entry.queued_task_id = task_id + + # Now store generate the queueing related filename + queue_filepath = app.queue_store.get_unique_filepath( + ['media_entries', + task_id, + secure_filename(filename)]) + + # queue appropriately + queue_file = app.queue_store.get_file( + queue_filepath, 'wb') + + # Add queued filename to the entry + entry.queued_media_file = queue_filepath + + return queue_file + + +def run_process_media(entry, feed_url=None): + """Process the media asynchronously + + :param entry: MediaEntry() instance to be processed. + :param feed_url: A string indicating the feed_url that the PuSH servers + should be notified of. This will be sth like: `request.urlgen( + 'mediagoblin.user_pages.atom_feed',qualified=True, + user=request.user.username)`""" + try: + process_media.apply_async( + [entry.id, feed_url], {}, + task_id=entry.queued_task_id) + except BaseException as exc: + # The purpose of this section is because when running in "lazy" + # or always-eager-with-exceptions-propagated celery mode that + # the failure handling won't happen on Celery end. Since we + # expect a lot of users to run things in this way we have to + # capture stuff here. + # + # ... not completely the diaper pattern because the + # exception is re-raised :) + mark_entry_failed(entry.id, exc) + # re-raise the exception + raise diff --git a/mediagoblin/submit/routing.py b/mediagoblin/submit/routing.py new file mode 100644 index 00000000..085344fd --- /dev/null +++ b/mediagoblin/submit/routing.py @@ -0,0 +1,21 @@ +# 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/>. + +from mediagoblin.tools.routing import add_route + +add_route('mediagoblin.submit.start', + '/submit/', 'mediagoblin.submit.views:submit_start') +add_route('mediagoblin.submit.collection', '/submit/collection', 'mediagoblin.submit.views:add_collection') diff --git a/mediagoblin/submit/views.py b/mediagoblin/submit/views.py new file mode 100644 index 00000000..a70c89b4 --- /dev/null +++ b/mediagoblin/submit/views.py @@ -0,0 +1,153 @@ +# 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/>. + +from mediagoblin import messages +import mediagoblin.mg_globals as mg_globals +from os.path import splitext + +import logging + +_log = logging.getLogger(__name__) + + +from mediagoblin.tools.text import convert_to_tag_list_of_dicts +from mediagoblin.tools.translate import pass_to_ugettext as _ +from mediagoblin.tools.response import render_to_response, redirect +from mediagoblin.decorators import require_active_login +from mediagoblin.submit import forms as submit_forms +from mediagoblin.messages import add_message, SUCCESS +from mediagoblin.media_types import sniff_media, \ + InvalidFileType, FileTypeNotSupported +from mediagoblin.submit.lib import check_file_field, prepare_queue_task, \ + run_process_media, new_upload_entry + + +@require_active_login +def submit_start(request): + """ + First view for submitting a file. + """ + submit_form = submit_forms.SubmitStartForm(request.form, + license=request.user.license_preference) + + if request.method == 'POST' and submit_form.validate(): + if not check_file_field(request, 'file'): + submit_form.file.errors.append( + _(u'You must provide a file.')) + else: + try: + filename = request.files['file'].filename + + # Sniff the submitted media to determine which + # media plugin should handle processing + media_type, media_manager = sniff_media( + request.files['file']) + + # create entry and save in database + entry = new_upload_entry(request.user) + entry.media_type = unicode(media_type) + entry.title = ( + unicode(submit_form.title.data) + or unicode(splitext(filename)[0])) + + entry.description = unicode(submit_form.description.data) + + entry.license = unicode(submit_form.license.data) or None + + # Process the user's folksonomy "tags" + entry.tags = convert_to_tag_list_of_dicts( + submit_form.tags.data) + + # Generate a slug from the title + entry.generate_slug() + + queue_file = prepare_queue_task(request.app, entry, filename) + + with queue_file: + queue_file.write(request.files['file'].stream.read()) + + # Save now so we have this data before kicking off processing + entry.save() + + # Pass off to processing + # + # (... don't change entry after this point to avoid race + # conditions with changes to the document via processing code) + feed_url = request.urlgen( + 'mediagoblin.user_pages.atom_feed', + qualified=True, user=request.user.username) + run_process_media(entry, feed_url) + add_message(request, SUCCESS, _('Woohoo! Submitted!')) + + return redirect(request, "mediagoblin.user_pages.user_home", + user=request.user.username) + except Exception as e: + ''' + This section is intended to catch exceptions raised in + mediagoblin.media_types + ''' + if isinstance(e, InvalidFileType) or \ + isinstance(e, FileTypeNotSupported): + submit_form.file.errors.append( + e) + else: + raise + + return render_to_response( + request, + 'mediagoblin/submit/start.html', + {'submit_form': submit_form, + 'app_config': mg_globals.app_config}) + + +@require_active_login +def add_collection(request, media=None): + """ + View to create a new collection + """ + submit_form = submit_forms.AddCollectionForm(request.form) + + if request.method == 'POST' and submit_form.validate(): + collection = request.db.Collection() + + collection.title = unicode(submit_form.title.data) + collection.description = unicode(submit_form.description.data) + collection.creator = request.user.id + collection.generate_slug() + + # Make sure this user isn't duplicating an existing collection + existing_collection = request.db.Collection.find_one({ + 'creator': request.user.id, + 'title':collection.title}) + + if existing_collection: + add_message(request, messages.ERROR, + _('You already have a collection called "%s"!') \ + % collection.title) + else: + collection.save() + + add_message(request, SUCCESS, + _('Collection "%s" added!') % collection.title) + + return redirect(request, "mediagoblin.user_pages.user_home", + user=request.user.username) + + return render_to_response( + request, + 'mediagoblin/submit/collection.html', + {'submit_form': submit_form, + 'app_config': mg_globals.app_config}) |