aboutsummaryrefslogtreecommitdiffstats
path: root/mediagoblin/submit
diff options
context:
space:
mode:
Diffstat (limited to 'mediagoblin/submit')
-rw-r--r--mediagoblin/submit/__init__.py15
-rw-r--r--mediagoblin/submit/forms.py53
-rw-r--r--mediagoblin/submit/lib.py102
-rw-r--r--mediagoblin/submit/routing.py21
-rw-r--r--mediagoblin/submit/views.py153
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})