aboutsummaryrefslogtreecommitdiffstats
path: root/mediagoblin
diff options
context:
space:
mode:
Diffstat (limited to 'mediagoblin')
-rw-r--r--mediagoblin/api/views.py9
-rw-r--r--mediagoblin/auth/tools.py18
-rw-r--r--mediagoblin/auth/views.py6
-rw-r--r--mediagoblin/config_spec.ini3
-rw-r--r--mediagoblin/db/migration_tools.py5
-rw-r--r--mediagoblin/db/migrations/alembic.ini56
-rw-r--r--mediagoblin/db/migrations/env.py3
-rw-r--r--mediagoblin/db/models.py61
-rw-r--r--mediagoblin/db/models_v0.py342
-rw-r--r--mediagoblin/decorators.py3
-rw-r--r--mediagoblin/edit/views.py33
-rw-r--r--mediagoblin/gmg_commands/addmedia.py11
-rw-r--r--mediagoblin/gmg_commands/batchaddmedia.py24
-rw-r--r--mediagoblin/gmg_commands/dbupdate.py6
-rw-r--r--mediagoblin/i18n/es/mediagoblin.po2
-rw-r--r--mediagoblin/init/config.py1
-rw-r--r--mediagoblin/listings/views.py55
-rw-r--r--mediagoblin/media_types/blog/models.py6
-rw-r--r--mediagoblin/media_types/blog/templates/mediagoblin/blog/blog_admin_dashboard.html4
-rw-r--r--mediagoblin/media_types/blog/templates/mediagoblin/blog/blogpost_draft_view.html2
-rw-r--r--mediagoblin/media_types/blog/templates/mediagoblin/blog/list_of_blogs.html2
-rw-r--r--mediagoblin/media_types/blog/views.py4
-rw-r--r--mediagoblin/media_types/video/models.py2
-rw-r--r--mediagoblin/media_types/video/processing.py21
-rw-r--r--mediagoblin/media_types/video/transcoders.py8
-rw-r--r--mediagoblin/plugins/api/views.py3
-rw-r--r--mediagoblin/plugins/basic_auth/README.rst2
-rw-r--r--mediagoblin/plugins/basic_auth/forms.py2
-rw-r--r--mediagoblin/plugins/flatpagesfile/README.rst8
-rw-r--r--mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html5
-rw-r--r--mediagoblin/plugins/ldap/README.rst18
-rw-r--r--mediagoblin/plugins/metadata_display/templates/mediagoblin/plugins/metadata_display/metadata_table.html.orig60
-rw-r--r--mediagoblin/plugins/openid/README.rst12
-rw-r--r--mediagoblin/plugins/piwigo/views.py5
-rw-r--r--mediagoblin/plugins/trim_whitespace/README.rst2
-rw-r--r--mediagoblin/processing/task.py3
-rw-r--r--mediagoblin/static/css/audio.css3
-rw-r--r--mediagoblin/static/css/base.css13
-rw-r--r--mediagoblin/static/js/audio.js24
-rw-r--r--mediagoblin/static/js/geolocation-map.js6
-rw-r--r--mediagoblin/static/js/header_dropdown.js24
-rw-r--r--mediagoblin/static/js/post_comment.js63
-rw-r--r--mediagoblin/static/js/show_password.js2
-rw-r--r--mediagoblin/submit/lib.py17
-rw-r--r--mediagoblin/submit/views.py1
-rw-r--r--mediagoblin/templates/mediagoblin/api/host-meta.xml (renamed from mediagoblin/templates/mediagoblin/federation/host-meta.xml)0
-rw-r--r--mediagoblin/templates/mediagoblin/api/oob.html4
-rw-r--r--mediagoblin/templates/mediagoblin/auth/register.html1
-rw-r--r--mediagoblin/templates/mediagoblin/media_displays/audio.html6
-rw-r--r--mediagoblin/templates/mediagoblin/moderation/media_panel.html1
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/blog_media.html4
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/media.html4
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/processing_panel.html2
-rw-r--r--mediagoblin/templates/mediagoblin/utils/collection_gallery.html8
-rw-r--r--mediagoblin/templates/mediagoblin/utils/prev_next.html15
-rw-r--r--mediagoblin/templates/mediagoblin/utils/wtforms.html4
-rw-r--r--mediagoblin/tests/.gitignore1
-rw-r--r--mediagoblin/tests/fake_carrot_conf_good.ini2
-rw-r--r--mediagoblin/tests/resources.py1
-rw-r--r--mediagoblin/tests/test_api.py90
-rw-r--r--mediagoblin/tests/test_auth.py50
-rw-r--r--mediagoblin/tests/test_celery_setup.py5
-rw-r--r--mediagoblin/tests/test_config.py4
-rw-r--r--mediagoblin/tests/test_exif.py17
-rw-r--r--mediagoblin/tests/test_exif/bad-gps.jpgbin0 -> 141096 bytes
-rw-r--r--mediagoblin/tests/test_tools.py57
-rw-r--r--mediagoblin/tests/test_util.py29
-rw-r--r--mediagoblin/tests/tools.py22
-rw-r--r--mediagoblin/themes/airy/assets/css/airy.css4
-rw-r--r--mediagoblin/tools/exif.py25
-rw-r--r--mediagoblin/tools/licenses.py37
-rw-r--r--mediagoblin/tools/mail.py32
-rw-r--r--mediagoblin/tools/pagination.py7
-rw-r--r--mediagoblin/tools/theme.py8
-rw-r--r--mediagoblin/user_pages/views.py70
75 files changed, 789 insertions, 681 deletions
diff --git a/mediagoblin/api/views.py b/mediagoblin/api/views.py
index 74181fde..b25300f0 100644
--- a/mediagoblin/api/views.py
+++ b/mediagoblin/api/views.py
@@ -115,8 +115,13 @@ def uploads_endpoint(request):
)
mimetype = request.headers["Content-Type"]
- filename = mimetypes.guess_all_extensions(mimetype)
- filename = 'unknown' + filename[0] if filename else filename
+
+ if "X-File-Name" in request.headers:
+ filename = request.headers["X-File-Name"]
+ else:
+ filename = mimetypes.guess_all_extensions(mimetype)
+ filename = 'unknown' + filename[0] if filename else filename
+
file_data = FileStorage(
stream=io.BytesIO(request.data),
filename=filename,
diff --git a/mediagoblin/auth/tools.py b/mediagoblin/auth/tools.py
index 9c16a980..ae6fadf6 100644
--- a/mediagoblin/auth/tools.py
+++ b/mediagoblin/auth/tools.py
@@ -34,14 +34,19 @@ from mediagoblin import auth
_log = logging.getLogger(__name__)
-def normalize_user_or_email_field(allow_email=True, allow_user=True):
- """
- Check if we were passed a field that matches a username and/or email
+def normalize_user_or_email_field(allow_email=True, allow_user=True,
+ is_login=False):
+ """Check if we were passed a field that matches a username and/or email
pattern.
This is useful for fields that can take either a username or email
- address. Use the parameters if you want to only allow a username for
- instance"""
+ address. Use the parameters if you want to only allow a username
+ for instance
+
+ is_login : bool
+ If is_login is True, does not check the length of username.
+
+ """
message = _(u'Invalid User name or email address.')
nomail_msg = _(u"This field does not take email addresses.")
nouser_msg = _(u"This field requires an email address.")
@@ -56,7 +61,8 @@ def normalize_user_or_email_field(allow_email=True, allow_user=True):
else: # lower case user names
if not allow_user:
raise wtforms.ValidationError(nouser_msg)
- wtforms.validators.Length(min=3, max=30)(form, field)
+ if not is_login:
+ wtforms.validators.Length(min=3, max=30)(form, field)
wtforms.validators.Regexp(r'^[-_\w]+$')(form, field)
field.data = field.data.lower()
if field.data is None: # should not happen, but be cautious anyway
diff --git a/mediagoblin/auth/views.py b/mediagoblin/auth/views.py
index 2f95fd81..fb8e7265 100644
--- a/mediagoblin/auth/views.py
+++ b/mediagoblin/auth/views.py
@@ -14,6 +14,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import logging
+
import six
from itsdangerous import BadSignature
@@ -29,6 +31,8 @@ from mediagoblin.tools.pluginapi import hook_handle
from mediagoblin.auth.tools import (send_verification_email, register_user,
check_login_simple)
+_log = logging.getLogger(__name__)
+
@allow_registration
@auth_enabled
@@ -105,6 +109,8 @@ def login(request):
return redirect(request, "index")
login_failed = True
+ remote_addr = request.access_route[-1] or request.remote_addr
+ _log.warn("Failed login attempt from %r", remote_addr)
return render_to_response(
request,
diff --git a/mediagoblin/config_spec.ini b/mediagoblin/config_spec.ini
index 0a8da73e..bd3003d0 100644
--- a/mediagoblin/config_spec.ini
+++ b/mediagoblin/config_spec.ini
@@ -153,8 +153,7 @@ CELERY_RESULT_BACKEND = string(default="database")
CELERY_RESULT_DBURI = string(default="sqlite:///%(here)s/celery.db")
# default kombu stuff
-BROKER_TRANSPORT = string(default="sqlalchemy")
-BROKER_URL = string(default="sqlite:///%(here)s/kombu.db")
+BROKER_URL = string(default="amqp://")
# known booleans
CELERY_RESULT_PERSISTENT = boolean()
diff --git a/mediagoblin/db/migration_tools.py b/mediagoblin/db/migration_tools.py
index f4273fa0..852f35ee 100644
--- a/mediagoblin/db/migration_tools.py
+++ b/mediagoblin/db/migration_tools.py
@@ -365,9 +365,8 @@ def build_alembic_config(global_config, cmd_options, session):
configuration. Initialize the database session appropriately
as well.
"""
- root_dir = os.path.abspath(os.path.dirname(os.path.dirname(
- os.path.dirname(__file__))))
- alembic_cfg_path = os.path.join(root_dir, 'alembic.ini')
+ alembic_dir = os.path.join(os.path.dirname(__file__), 'migrations')
+ alembic_cfg_path = os.path.join(alembic_dir, 'alembic.ini')
cfg = Config(alembic_cfg_path,
cmd_opts=cmd_options)
cfg.attributes["session"] = session
diff --git a/mediagoblin/db/migrations/alembic.ini b/mediagoblin/db/migrations/alembic.ini
new file mode 100644
index 00000000..4f7fc115
--- /dev/null
+++ b/mediagoblin/db/migrations/alembic.ini
@@ -0,0 +1,56 @@
+# A generic, single database configuration.
+
+[alembic]
+# path to migration scripts
+script_location = %(here)s
+
+# template used to generate migration files
+# file_template = %%(rev)s_%%(slug)s
+
+# max length of characters to apply to the
+# "slug" field
+#truncate_slug_length = 40
+
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
+# set to 'true' to allow .pyc and .pyo files without
+# a source .py file to be detected as revisions in the
+# versions/ directory
+# sourceless = false
+
+# Logging configuration
+[loggers]
+keys = root,sqlalchemy,alembic
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+qualname =
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+
+[logger_alembic]
+level = INFO
+handlers =
+qualname = alembic
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
diff --git a/mediagoblin/db/migrations/env.py b/mediagoblin/db/migrations/env.py
index 43b7b247..a6d05cd1 100644
--- a/mediagoblin/db/migrations/env.py
+++ b/mediagoblin/db/migrations/env.py
@@ -48,7 +48,7 @@ def run_migrations_online():
and associate a connection with the context.
"""
- connection = config.attributes["session"].get_bind()
+ connection = config.attributes["session"].connection()
context.configure(
connection=connection,
target_metadata=target_metadata
@@ -61,4 +61,3 @@ if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
-
diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py
index 9bbb252b..c19fe4da 100644
--- a/mediagoblin/db/models.py
+++ b/mediagoblin/db/models.py
@@ -43,6 +43,7 @@ from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, \
from mediagoblin.tools.files import delete_media_files
from mediagoblin.tools.common import import_component
from mediagoblin.tools.routing import extract_url_arguments
+from mediagoblin.tools.text import convert_to_tag_list_of_dicts
import six
from six.moves.urllib.parse import urljoin
@@ -596,6 +597,16 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin):
# fail_error
@property
+ def get_uploader(self):
+ # for compatibility
+ return self.get_actor
+
+ @property
+ def uploader(self):
+ # for compatibility
+ return self.actor
+
+ @property
def collections(self):
""" Get any collections that this MediaEntry is in """
return list(Collection.query.join(Collection.collection_items).join(
@@ -617,9 +628,9 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin):
query = query.order_by(Comment.added.asc())
else:
query = query.order_by(Comment.added.desc())
-
+
return query
-
+
def url_to_prev(self, urlgen):
"""get the next 'newer' entry by this user"""
media = MediaEntry.query.filter(
@@ -769,7 +780,6 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin):
"self": {
"href": public_id,
},
-
}
}
@@ -785,6 +795,12 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin):
if self.location:
context["location"] = self.get_location.serialize(request)
+ # Always show tags, even if empty list
+ if self.tags:
+ context["tags"] = [tag['name'] for tag in self.tags]
+ else:
+ context["tags"] = []
+
if show_comments:
comments = [
l.comment().serialize(request) for l in self.get_comments()]
@@ -832,6 +848,9 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin):
if "location" in data:
License.create(data["location"], self)
+ if "tags" in data:
+ self.tags = convert_to_tag_list_of_dicts(', '.join(data["tags"]))
+
return True
class FileKeynames(Base):
@@ -966,7 +985,7 @@ class MediaTag(Base):
class Comment(Base):
"""
Link table between a response and another object that can have replies.
-
+
This acts as a link table between an object and the comments on it, it's
done like this so that you can look up all the comments without knowing
whhich comments are on an object before hand. Any object can be a comment
@@ -977,7 +996,7 @@ class Comment(Base):
__tablename__ = "core__comment_links"
id = Column(Integer, primary_key=True)
-
+
# The GMR to the object the comment is on.
target_id = Column(
Integer,
@@ -1006,7 +1025,25 @@ class Comment(Base):
# When it was added
added = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
-
+
+ @property
+ def get_author(self):
+ # for compatibility
+ return self.comment().get_actor # noqa
+
+ def __getattr__(self, attr):
+ if attr.startswith('_'):
+ # if attr starts with '_', then it's probably some internal
+ # sqlalchemy variable. Since __getattr__ is called when
+ # non-existing attributes are being accessed, we should not try to
+ # fetch it from self.comment()
+ raise AttributeError
+ try:
+ _log.debug('Old attr is being accessed: {0}'.format(attr))
+ return getattr(self.comment(), attr) # noqa
+ except Exception as e:
+ _log.error(e)
+ raise
class TextComment(Base, TextCommentMixin, CommentingMixin):
"""
@@ -1040,7 +1077,7 @@ class TextComment(Base, TextCommentMixin, CommentingMixin):
if target is None:
target = {}
else:
- target = target.serialize(request, show_comments=False)
+ target = target.serialize(request, show_comments=False)
author = self.get_actor
@@ -1068,7 +1105,7 @@ class TextComment(Base, TextCommentMixin, CommentingMixin):
if "location" in data:
Location.create(data["location"], self)
-
+
# Handle changing the reply ID
if "inReplyTo" in data:
# Validate that the ID is correct
@@ -1099,7 +1136,7 @@ class TextComment(Base, TextCommentMixin, CommentingMixin):
link.target = media
link.comment = self
link.save()
-
+
return True
class Collection(Base, CollectionMixin, CommentingMixin):
@@ -1298,7 +1335,7 @@ class Notification(Base):
seen = Column(Boolean, default=lambda: False, index=True)
user = relationship(
User,
- backref=backref('notifications', cascade='all, delete-orphan'))
+ backref=backref('notifications', cascade='all, delete-orphan'))
def __repr__(self):
return '<{klass} #{id}: {user}: {subject} ({seen})>'.format(
@@ -1343,7 +1380,7 @@ class Report(Base):
which points to the reported object.
"""
__tablename__ = 'core__reports'
-
+
id = Column(Integer, primary_key=True)
reporter_id = Column(Integer, ForeignKey(User.id), nullable=False)
reporter = relationship(
@@ -1371,7 +1408,7 @@ class Report(Base):
resolved = Column(DateTime)
result = Column(UnicodeText)
-
+
object_id = Column(Integer, ForeignKey(GenericModelReference.id), nullable=True)
object_helper = relationship(GenericModelReference)
obj = association_proxy("object_helper", "get_object",
diff --git a/mediagoblin/db/models_v0.py b/mediagoblin/db/models_v0.py
deleted file mode 100644
index bdedec2e..00000000
--- a/mediagoblin/db/models_v0.py
+++ /dev/null
@@ -1,342 +0,0 @@
-# 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/>.
-
-"""
-TODO: indexes on foreignkeys, where useful.
-"""
-
-###########################################################################
-# WHAT IS THIS FILE?
-# ------------------
-#
-# Upon occasion, someone runs into this file and wonders why we have
-# both a models.py and a models_v0.py.
-#
-# The short of it is: you can ignore this file.
-#
-# The long version is, in two parts:
-#
-# - We used to use MongoDB, then we switched to SQL and SQLAlchemy.
-# We needed to convert peoples' databases; the script we had would
-# switch them to the first version right after Mongo, convert over
-# all their tables, then run any migrations that were added after.
-#
-# - That script is now removed, but there is some discussion of
-# writing a test that would set us at the first SQL migration and
-# run everything after. If we wrote that, this file would still be
-# useful. But for now, it's legacy!
-#
-###########################################################################
-
-
-import datetime
-import sys
-
-from sqlalchemy import (
- Column, Integer, Unicode, UnicodeText, DateTime, Boolean, ForeignKey,
- UniqueConstraint, PrimaryKeyConstraint, SmallInteger, Float)
-from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import relationship, backref
-from sqlalchemy.orm.collections import attribute_mapped_collection
-from sqlalchemy.ext.associationproxy import association_proxy
-from sqlalchemy.util import memoized_property
-
-from mediagoblin.db.extratypes import PathTupleWithSlashes, JSONEncoded
-from mediagoblin.db.base import GMGTableBase, Session
-
-
-Base_v0 = declarative_base(cls=GMGTableBase)
-
-
-class User(Base_v0):
- """
- TODO: We should consider moving some rarely used fields
- into some sort of "shadow" table.
- """
- __tablename__ = "core__users"
-
- id = Column(Integer, primary_key=True)
- username = Column(Unicode, nullable=False, unique=True)
- email = Column(Unicode, nullable=False)
- created = Column(DateTime, nullable=False, default=datetime.datetime.now)
- pw_hash = Column(Unicode, nullable=False)
- email_verified = Column(Boolean, default=False)
- status = Column(Unicode, default=u"needs_email_verification", nullable=False)
- verification_key = Column(Unicode)
- is_admin = Column(Boolean, default=False, nullable=False)
- url = Column(Unicode)
- bio = Column(UnicodeText) # ??
- fp_verification_key = Column(Unicode)
- fp_token_expire = Column(DateTime)
-
- ## TODO
- # plugin data would be in a separate model
-
-
-class MediaEntry(Base_v0):
- """
- TODO: Consider fetching the media_files using join
- """
- __tablename__ = "core__media_entries"
-
- id = Column(Integer, primary_key=True)
- uploader = Column(Integer, ForeignKey(User.id), nullable=False, index=True)
- title = Column(Unicode, nullable=False)
- slug = Column(Unicode)
- created = Column(DateTime, nullable=False, default=datetime.datetime.now,
- index=True)
- description = Column(UnicodeText) # ??
- media_type = Column(Unicode, nullable=False)
- state = Column(Unicode, default=u'unprocessed', nullable=False)
- # or use sqlalchemy.types.Enum?
- license = Column(Unicode)
-
- fail_error = Column(Unicode)
- fail_metadata = Column(JSONEncoded)
-
- queued_media_file = Column(PathTupleWithSlashes)
-
- queued_task_id = Column(Unicode)
-
- __table_args__ = (
- UniqueConstraint('uploader', 'slug'),
- {})
-
- get_uploader = relationship(User)
-
- media_files_helper = relationship("MediaFile",
- collection_class=attribute_mapped_collection("name"),
- cascade="all, delete-orphan"
- )
-
- attachment_files_helper = relationship("MediaAttachmentFile",
- cascade="all, delete-orphan",
- order_by="MediaAttachmentFile.created"
- )
-
- tags_helper = relationship("MediaTag",
- cascade="all, delete-orphan"
- )
-
- def media_data_init(self, **kwargs):
- """
- Initialize or update the contents of a media entry's media_data row
- """
- session = Session()
-
- media_data = session.query(self.media_data_table).filter_by(
- media_entry=self.id).first()
-
- # No media data, so actually add a new one
- if media_data is None:
- media_data = self.media_data_table(
- media_entry=self.id,
- **kwargs)
- session.add(media_data)
- # Update old media data
- else:
- for field, value in kwargs.iteritems():
- setattr(media_data, field, value)
-
- @memoized_property
- def media_data_table(self):
- # TODO: memoize this
- models_module = self.media_type + '.models'
- __import__(models_module)
- return sys.modules[models_module].DATA_MODEL
-
-
-class FileKeynames(Base_v0):
- """
- keywords for various places.
- currently the MediaFile keys
- """
- __tablename__ = "core__file_keynames"
- id = Column(Integer, primary_key=True)
- name = Column(Unicode, unique=True)
-
- def __repr__(self):
- return "<FileKeyname %r: %r>" % (self.id, self.name)
-
- @classmethod
- def find_or_new(cls, name):
- t = cls.query.filter_by(name=name).first()
- if t is not None:
- return t
- return cls(name=name)
-
-
-class MediaFile(Base_v0):
- """
- TODO: Highly consider moving "name" into a new table.
- TODO: Consider preloading said table in software
- """
- __tablename__ = "core__mediafiles"
-
- media_entry = Column(
- Integer, ForeignKey(MediaEntry.id),
- nullable=False)
- name_id = Column(SmallInteger, ForeignKey(FileKeynames.id), nullable=False)
- file_path = Column(PathTupleWithSlashes)
-
- __table_args__ = (
- PrimaryKeyConstraint('media_entry', 'name_id'),
- {})
-
- def __repr__(self):
- return "<MediaFile %s: %r>" % (self.name, self.file_path)
-
- name_helper = relationship(FileKeynames, lazy="joined", innerjoin=True)
- name = association_proxy('name_helper', 'name',
- creator=FileKeynames.find_or_new
- )
-
-
-class MediaAttachmentFile(Base_v0):
- __tablename__ = "core__attachment_files"
-
- id = Column(Integer, primary_key=True)
- media_entry = Column(
- Integer, ForeignKey(MediaEntry.id),
- nullable=False)
- name = Column(Unicode, nullable=False)
- filepath = Column(PathTupleWithSlashes)
- created = Column(DateTime, nullable=False, default=datetime.datetime.now)
-
-
-class Tag(Base_v0):
- __tablename__ = "core__tags"
-
- id = Column(Integer, primary_key=True)
- slug = Column(Unicode, nullable=False, unique=True)
-
- def __repr__(self):
- return "<Tag %r: %r>" % (self.id, self.slug)
-
- @classmethod
- def find_or_new(cls, slug):
- t = cls.query.filter_by(slug=slug).first()
- if t is not None:
- return t
- return cls(slug=slug)
-
-
-class MediaTag(Base_v0):
- __tablename__ = "core__media_tags"
-
- id = Column(Integer, primary_key=True)
- media_entry = Column(
- Integer, ForeignKey(MediaEntry.id),
- nullable=False, index=True)
- tag = Column(Integer, ForeignKey(Tag.id), nullable=False, index=True)
- name = Column(Unicode)
- # created = Column(DateTime, nullable=False, default=datetime.datetime.now)
-
- __table_args__ = (
- UniqueConstraint('tag', 'media_entry'),
- {})
-
- tag_helper = relationship(Tag)
- slug = association_proxy('tag_helper', 'slug',
- creator=Tag.find_or_new
- )
-
- def __init__(self, name=None, slug=None):
- Base_v0.__init__(self)
- if name is not None:
- self.name = name
- if slug is not None:
- self.tag_helper = Tag.find_or_new(slug)
-
-
-class MediaComment(Base_v0):
- __tablename__ = "core__media_comments"
-
- id = Column(Integer, primary_key=True)
- media_entry = Column(
- Integer, ForeignKey(MediaEntry.id), nullable=False, index=True)
- author = Column(Integer, ForeignKey(User.id), nullable=False)
- created = Column(DateTime, nullable=False, default=datetime.datetime.now)
- content = Column(UnicodeText, nullable=False)
-
- get_author = relationship(User)
-
-
-class ImageData(Base_v0):
- __tablename__ = "image__mediadata"
-
- # The primary key *and* reference to the main media_entry
- media_entry = Column(Integer, ForeignKey('core__media_entries.id'),
- primary_key=True)
- get_media_entry = relationship("MediaEntry",
- backref=backref("image__media_data", cascade="all, delete-orphan"))
-
- width = Column(Integer)
- height = Column(Integer)
- exif_all = Column(JSONEncoded)
- gps_longitude = Column(Float)
- gps_latitude = Column(Float)
- gps_altitude = Column(Float)
- gps_direction = Column(Float)
-
-
-class VideoData(Base_v0):
- __tablename__ = "video__mediadata"
-
- # The primary key *and* reference to the main media_entry
- media_entry = Column(Integer, ForeignKey('core__media_entries.id'),
- primary_key=True)
- get_media_entry = relationship("MediaEntry",
- backref=backref("video__media_data", cascade="all, delete-orphan"))
-
- width = Column(SmallInteger)
- height = Column(SmallInteger)
-
-
-class AsciiData(Base_v0):
- __tablename__ = "ascii__mediadata"
-
- # The primary key *and* reference to the main media_entry
- media_entry = Column(Integer, ForeignKey('core__media_entries.id'),
- primary_key=True)
- get_media_entry = relationship("MediaEntry",
- backref=backref("ascii__media_data", cascade="all, delete-orphan"))
-
-
-class AudioData(Base_v0):
- __tablename__ = "audio__mediadata"
-
- # The primary key *and* reference to the main media_entry
- media_entry = Column(Integer, ForeignKey('core__media_entries.id'),
- primary_key=True)
- get_media_entry = relationship("MediaEntry",
- backref=backref("audio__media_data", cascade="all, delete-orphan"))
-
-
-######################################################
-# Special, migrations-tracking table
-#
-# Not listed in MODELS because this is special and not
-# really migrated, but used for migrations (for now)
-######################################################
-
-class MigrationData(Base_v0):
- __tablename__ = "core__migrations"
-
- name = Column(Unicode, primary_key=True)
- version = Column(Integer, nullable=False, default=0)
-
-######################################################
diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py
index daeddb3f..2b8343b8 100644
--- a/mediagoblin/decorators.py
+++ b/mediagoblin/decorators.py
@@ -268,8 +268,7 @@ def get_media_entry_by_id(controller):
@wraps(controller)
def wrapper(request, *args, **kwargs):
media = MediaEntry.query.filter_by(
- id=request.matchdict['media_id'],
- state=u'processed').first()
+ id=request.matchdict['media_id']).first()
# Still no media? Okay, 404.
if not media:
return render_404(request)
diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py
index 69f69da5..717241e8 100644
--- a/mediagoblin/edit/views.py
+++ b/mediagoblin/edit/views.py
@@ -1,4 +1,4 @@
-# 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
@@ -55,6 +55,10 @@ import mimetypes
@get_media_entry_by_id
@require_active_login
def edit_media(request, media):
+ # If media is not processed, return NotFound.
+ if not media.state == u'processed':
+ return render_404(request)
+
if not may_edit_media(request, media):
raise Forbidden("User may not edit this media")
@@ -66,7 +70,7 @@ def edit_media(request, media):
license=media.license)
form = forms.EditForm(
- request.form,
+ request.method=='POST' and request.form or None,
**defaults)
if request.method == 'POST' and form.validate():
@@ -115,6 +119,10 @@ UNSAFE_MIMETYPES = [
@get_media_entry_by_id
@require_active_login
def edit_attachments(request, media):
+ # If media is not processed, return NotFound.
+ if not media.state == u'processed':
+ return render_404(request)
+
if mg_globals.app_config['allow_attachments']:
form = forms.EditAttachmentsForm()
@@ -211,7 +219,8 @@ def edit_profile(request, url_user=None):
else:
location = user.get_location.name
- form = forms.EditProfileForm(request.form,
+ form = forms.EditProfileForm(
+ request.method == 'POST' and request.form or None,
url=user.url,
bio=user.bio,
location=location)
@@ -227,6 +236,8 @@ def edit_profile(request, url_user=None):
location = user.get_location
location.name = six.text_type(form.location.data)
location.save()
+ else:
+ user.location = None
user.save()
@@ -252,7 +263,8 @@ EMAIL_VERIFICATION_TEMPLATE = (
@require_active_login
def edit_account(request):
user = request.user
- form = forms.EditAccountForm(request.form,
+ form = forms.EditAccountForm(
+ request.method == 'POST' and request.form or None,
wants_comment_notification=user.wants_comment_notification,
license_preference=user.license_preference,
wants_notifications=user.wants_notifications)
@@ -350,7 +362,7 @@ def edit_collection(request, collection):
description=collection.description)
form = forms.EditCollectionForm(
- request.form,
+ request.method == 'POST' and request.form or None,
**defaults)
if request.method == 'POST' and form.validate():
@@ -443,9 +455,11 @@ def verify_email(request):
user=user.username)
+@require_active_login
def change_email(request):
""" View to change the user's email """
- form = forms.ChangeEmailForm(request.form)
+ form = forms.ChangeEmailForm(
+ request.method == 'POST' and request.form or None)
user = request.user
# If no password authentication, no need to enter a password
@@ -498,7 +512,12 @@ def change_email(request):
@require_active_login
@get_media_entry_by_id
def edit_metadata(request, media):
- form = forms.EditMetaDataForm(request.form)
+ # If media is not processed, return NotFound.
+ if not media.state == u'processed':
+ return render_404(request)
+
+ form = forms.EditMetaDataForm(
+ request.method == 'POST' and request.form or None)
if request.method == "POST" and form.validate():
metadata_dict = dict([(row['identifier'],row['value'])
for row in form.media_metadata.data])
diff --git a/mediagoblin/gmg_commands/addmedia.py b/mediagoblin/gmg_commands/addmedia.py
index 8cbfc806..026f3495 100644
--- a/mediagoblin/gmg_commands/addmedia.py
+++ b/mediagoblin/gmg_commands/addmedia.py
@@ -56,6 +56,11 @@ def parser_setup(subparser):
help=(
"Slug for this media entry. "
"Will be autogenerated if unspecified."))
+ subparser.add_argument(
+ "-c", "--collection-slug",
+ help=(
+ "Slug of the collection for this media entry. "
+ "Should already exist."))
subparser.add_argument(
'--celery',
@@ -85,8 +90,6 @@ def addmedia(args):
print("Can't find a file with filename '%s'" % args.filename)
return
- upload_limit, max_file_size = get_upload_file_limits(user)
-
def maybe_unicodeify(some_string):
# this is kinda terrible
if some_string is None:
@@ -102,9 +105,9 @@ def addmedia(args):
submitted_file=open(abs_filename, 'rb'), filename=filename,
title=maybe_unicodeify(args.title),
description=maybe_unicodeify(args.description),
+ collection_slug=args.collection_slug,
license=maybe_unicodeify(args.license),
- tags_string=maybe_unicodeify(args.tags) or u"",
- upload_limit=upload_limit, max_file_size=max_file_size)
+ tags_string=maybe_unicodeify(args.tags) or u"")
except FileUploadLimit:
print("This file is larger than the upload limits for this site.")
except UserUploadLimit:
diff --git a/mediagoblin/gmg_commands/batchaddmedia.py b/mediagoblin/gmg_commands/batchaddmedia.py
index 2ad7e39e..55ed865b 100644
--- a/mediagoblin/gmg_commands/batchaddmedia.py
+++ b/mediagoblin/gmg_commands/batchaddmedia.py
@@ -19,6 +19,7 @@ from __future__ import print_function
import codecs
import csv
import os
+import sys
import requests
import six
@@ -28,8 +29,7 @@ from six.moves.urllib.parse import urlparse
from mediagoblin.db.models import LocalUser
from mediagoblin.gmg_commands import util as commands_util
from mediagoblin.submit.lib import (
- submit_media, get_upload_file_limits,
- FileUploadLimit, UserUploadLimit, UserPastUploadLimit)
+ submit_media, FileUploadLimit, UserUploadLimit, UserPastUploadLimit)
from mediagoblin.tools.metadata import compact_and_validate
from mediagoblin.tools.translate import pass_to_ugettext as _
from jsonschema.exceptions import ValidationError
@@ -73,9 +73,6 @@ def batchaddmedia(args):
username=args.username)))
return
- upload_limit, max_file_size = get_upload_file_limits(user)
- temp_files = []
-
if os.path.isfile(args.metadata_path):
metadata_path = args.metadata_path
@@ -87,7 +84,6 @@ def batchaddmedia(args):
abs_metadata_filename = os.path.abspath(metadata_path)
abs_metadata_dir = os.path.dirname(abs_metadata_filename)
- upload_limit, max_file_size = get_upload_file_limits(user)
def maybe_unicodeify(some_string):
# this is kinda terrible
@@ -101,7 +97,7 @@ def batchaddmedia(args):
contents = all_metadata.read()
media_metadata = parse_csv_file(contents)
- for media_id, file_metadata in media_metadata.iteritems():
+ for media_id, file_metadata in media_metadata.items():
files_attempted += 1
# In case the metadata was not uploaded initialize an empty dictionary.
json_ld_metadata = compact_and_validate({})
@@ -115,6 +111,7 @@ def batchaddmedia(args):
title = file_metadata.get('title') or file_metadata.get('dc:title')
description = (file_metadata.get('description') or
file_metadata.get('dc:description'))
+ collection_slug = file_metadata.get('collection-slug')
license = file_metadata.get('license')
try:
@@ -143,7 +140,7 @@ Metadata was not uploaded.""".format(
file_path = os.path.join(abs_metadata_dir, path)
file_abs_path = os.path.abspath(file_path)
try:
- media_file = file(file_abs_path, 'r')
+ media_file = open(file_abs_path, 'rb')
except IOError:
print(_(u"""\
FAIL: Local file {filename} could not be accessed.
@@ -157,10 +154,10 @@ FAIL: Local file {filename} could not be accessed.
filename=filename,
title=maybe_unicodeify(title),
description=maybe_unicodeify(description),
+ collection_slug=maybe_unicodeify(collection_slug),
license=maybe_unicodeify(license),
metadata=json_ld_metadata,
- tags_string=u"",
- upload_limit=upload_limit, max_file_size=max_file_size)
+ tags_string=u"")
print(_(u"""Successfully submitted {filename}!
Be sure to look at the Media Processing Panel on your website to be sure it
uploaded successfully.""".format(filename=filename)))
@@ -206,7 +203,12 @@ def parse_csv_file(file_contents):
# Build a dictionary
for index, line in enumerate(lines):
if line.isspace() or line == u'': continue
- values = unicode_csv_reader([line]).next()
+ if (sys.version_info[0] == 3):
+ # Python 3's csv.py supports Unicode out of the box.
+ reader = csv.reader([line])
+ else:
+ reader = unicode_csv_reader([line])
+ values = next(reader)
line_dict = dict([(key[i], val)
for i, val in enumerate(values)])
media_id = line_dict.get('id') or index
diff --git a/mediagoblin/gmg_commands/dbupdate.py b/mediagoblin/gmg_commands/dbupdate.py
index bafe76bb..2700ccbc 100644
--- a/mediagoblin/gmg_commands/dbupdate.py
+++ b/mediagoblin/gmg_commands/dbupdate.py
@@ -133,7 +133,9 @@ def run_alembic_migrations(db, app_config, global_config):
session = Session()
cfg = build_alembic_config(global_config, None, session)
- return command.upgrade(cfg, 'heads')
+ res = command.upgrade(cfg, 'heads')
+ session.commit()
+ return res
def run_dbupdate(app_config, global_config):
@@ -146,7 +148,7 @@ def run_dbupdate(app_config, global_config):
# Set up the database
db = setup_connection_and_db_from_config(app_config, migrations=True)
- # Do we have migrations
+ # Do we have migrations
should_run_sqam_migrations = db.engine.has_table("core__migrations") and \
sqam_migrations_to_run(db, app_config,
global_config)
diff --git a/mediagoblin/i18n/es/mediagoblin.po b/mediagoblin/i18n/es/mediagoblin.po
index 8fd27b62..bdc63e56 100644
--- a/mediagoblin/i18n/es/mediagoblin.po
+++ b/mediagoblin/i18n/es/mediagoblin.po
@@ -2645,7 +2645,7 @@ msgstr "más antiguo"
#: mediagoblin/templates/mediagoblin/utils/profile.html:36
msgid "Location"
-msgstr "Locación"
+msgstr "Lugar"
#: mediagoblin/templates/mediagoblin/utils/report.html:25
msgid "Report media"
diff --git a/mediagoblin/init/config.py b/mediagoblin/init/config.py
index a9189e8d..fe469156 100644
--- a/mediagoblin/init/config.py
+++ b/mediagoblin/init/config.py
@@ -123,6 +123,7 @@ def read_mediagoblin_config(config_path, config_spec_path=CONFIG_SPEC_PATH):
config = ConfigObj(
config_path,
configspec=config_spec,
+ encoding="UTF8",
interpolation="ConfigParser")
_setup_defaults(config, config_path, mainconfig_defaults)
diff --git a/mediagoblin/listings/views.py b/mediagoblin/listings/views.py
index f640cc95..6e1528ca 100644
--- a/mediagoblin/listings/views.py
+++ b/mediagoblin/listings/views.py
@@ -17,9 +17,10 @@
from mediagoblin import mg_globals
from mediagoblin.db.models import MediaEntry
from mediagoblin.db.util import media_entries_for_tag_slug
+from mediagoblin.decorators import uses_pagination
+from mediagoblin.plugins.api.tools import get_media_file_paths
from mediagoblin.tools.pagination import Pagination
from mediagoblin.tools.response import render_to_response
-from mediagoblin.decorators import uses_pagination
from werkzeug.contrib.atom import AtomFeed
@@ -72,19 +73,25 @@ def atom_feed(request):
tag_slug = request.matchdict.get(u'tag')
feed_title = "MediaGoblin Feed"
if tag_slug:
- cursor = media_entries_for_tag_slug(request.db, tag_slug)
+ feed_title += " for tag '%s'" % tag_slug
link = request.urlgen('mediagoblin.listings.tags_listing',
qualified=True, tag=tag_slug )
- feed_title += "for tag '%s'" % tag_slug
+ cursor = media_entries_for_tag_slug(request.db, tag_slug)
else: # all recent item feed
- cursor = MediaEntry.query.filter_by(state=u'processed')
+ feed_title += " for all recent items"
link = request.urlgen('index', qualified=True)
- feed_title += "for all recent items"
+ cursor = MediaEntry.query.filter_by(state=u'processed')
+ cursor = cursor.order_by(MediaEntry.created.desc())
+ cursor = cursor.limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS)
+
- atomlinks = [
- {'href': link,
- 'rel': 'alternate',
- 'type': 'text/html'}]
+ """
+ ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI)
+ """
+ atomlinks = [{
+ 'href': link,
+ 'rel': 'alternate',
+ 'type': 'text/html'}]
if mg_globals.app_config["push_urls"]:
for push_url in mg_globals.app_config["push_urls"]:
@@ -92,9 +99,6 @@ def atom_feed(request):
'rel': 'hub',
'href': push_url})
- cursor = cursor.order_by(MediaEntry.created.desc())
- cursor = cursor.limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS)
-
feed = AtomFeed(
feed_title,
feed_url=request.url,
@@ -102,19 +106,30 @@ def atom_feed(request):
links=atomlinks)
for entry in cursor:
- feed.add(entry.get('title'),
- entry.description_html,
- id=entry.url_for_self(request.urlgen,qualified=True),
+ # Include a thumbnail image in content.
+ file_urls = get_media_file_paths(entry.media_files, request.urlgen)
+ if 'thumb' in file_urls:
+ content = u'<img src="{thumb}" alt='' /> {desc}'.format(
+ thumb=file_urls['thumb'], desc=entry.description_html)
+ else:
+ content = entry.description_html
+
+ feed.add(
+ entry.get('title'),
+ content,
+ id=entry.url_for_self(request.urlgen, qualified=True),
content_type='html',
- author={'name': entry.get_actor.username,
+ author={
+ 'name': entry.get_actor.username,
'uri': request.urlgen(
'mediagoblin.user_pages.user_home',
- qualified=True, user=entry.get_actor.username)},
+ qualified=True,
+ user=entry.get_actor.username)},
updated=entry.get('created'),
links=[{
- 'href':entry.url_for_self(
- request.urlgen,
- qualified=True),
+ 'href': entry.url_for_self(
+ request.urlgen,
+ qualified=True),
'rel': 'alternate',
'type': 'text/html'}])
diff --git a/mediagoblin/media_types/blog/models.py b/mediagoblin/media_types/blog/models.py
index 0e1ddf97..83f520c7 100644
--- a/mediagoblin/media_types/blog/models.py
+++ b/mediagoblin/media_types/blog/models.py
@@ -34,6 +34,7 @@ class BlogMixin(GenerateSlugMixin):
def check_slug_used(self, slug):
return check_blog_slug_used(self.author, slug, self.id)
+BLOG_BACKREF_NAME = "mediatype__blogs"
class Blog(Base, BlogMixin):
__tablename__ = "mediatype__blogs"
@@ -43,6 +44,7 @@ class Blog(Base, BlogMixin):
author = Column(Integer, ForeignKey(User.id), nullable=False, index=True) #similar to uploader
created = Column(DateTime, nullable=False, default=datetime.datetime.now, index=True)
slug = Column(Unicode)
+ get_author = relationship("User", backref=backref(BLOG_BACKREF_NAME, cascade="all, delete-orphan"))
@property
def slug_or_id(self):
@@ -66,7 +68,7 @@ class Blog(Base, BlogMixin):
-BACKREF_NAME = "blogpost__media_data"
+BLOG_POST_BACKREF_NAME = "blogpost__media_data"
class BlogPostData(Base):
__tablename__ = "blogpost__mediadata"
@@ -75,7 +77,7 @@ class BlogPostData(Base):
media_entry = Column(Integer, ForeignKey('core__media_entries.id'), primary_key=True)
blog = Column(Integer, ForeignKey('mediatype__blogs.id'), nullable=False)
get_media_entry = relationship("MediaEntry",
- backref=backref(BACKREF_NAME, uselist=False,
+ backref=backref(BLOG_POST_BACKREF_NAME, uselist=False,
cascade="all, delete-orphan"))
diff --git a/mediagoblin/media_types/blog/templates/mediagoblin/blog/blog_admin_dashboard.html b/mediagoblin/media_types/blog/templates/mediagoblin/blog/blog_admin_dashboard.html
index 3b881466..97408b59 100644
--- a/mediagoblin/media_types/blog/templates/mediagoblin/blog/blog_admin_dashboard.html
+++ b/mediagoblin/media_types/blog/templates/mediagoblin/blog/blog_admin_dashboard.html
@@ -53,7 +53,7 @@
{% set blog_delete_url = request.urlgen('mediagoblin.media_types.blog.blog_delete',
blog_slug=blog.slug,
user=request.user.username) %}
-<a class="button_action" href="{{ blog_delete_url }}">
+<a class="button_action button_warning" href="{{ blog_delete_url }}">
{%- trans %}Delete Blog{% endtrans -%}
</a>
</p>
@@ -90,7 +90,7 @@
media_id=blog_post.id) %}
<td>
<a class="button_action" href="{{ blogpost_edit_url }}">{% trans %}Edit{% endtrans %}</a>
- <a class="button_action" href="{{ blogpost_delete_url }}">{% trans %}Delete{% endtrans %}</a>
+ <a class="button_action button_warning" href="{{ blogpost_delete_url }}">{% trans %}Delete{% endtrans %}</a>
</td>
</tr>
{% endfor %}
diff --git a/mediagoblin/media_types/blog/templates/mediagoblin/blog/blogpost_draft_view.html b/mediagoblin/media_types/blog/templates/mediagoblin/blog/blogpost_draft_view.html
index 6d820550..7a53cc9e 100644
--- a/mediagoblin/media_types/blog/templates/mediagoblin/blog/blogpost_draft_view.html
+++ b/mediagoblin/media_types/blog/templates/mediagoblin/blog/blogpost_draft_view.html
@@ -33,7 +33,7 @@
user= blogpost.get_actor.username,
media_id=blogpost.id) %}
<a class="button_action" href="{{ blogpost_edit_url }}">{% trans %}Edit{% endtrans %}</a>
- <a class="button_action" href="{{ blogpost_delete_url }}">{% trans %}Delete{% endtrans %}</a>
+ <a class="button_action button_warning" href="{{ blogpost_delete_url }}">{% trans %}Delete{% endtrans %}</a>
{% endblock %}
diff --git a/mediagoblin/media_types/blog/templates/mediagoblin/blog/list_of_blogs.html b/mediagoblin/media_types/blog/templates/mediagoblin/blog/list_of_blogs.html
index 8c16daeb..bad33c8c 100644
--- a/mediagoblin/media_types/blog/templates/mediagoblin/blog/list_of_blogs.html
+++ b/mediagoblin/media_types/blog/templates/mediagoblin/blog/list_of_blogs.html
@@ -52,7 +52,7 @@
{% if request.user and request.user.username==user.username %}
<p>You have not created any blog yet.</p>
{% else %}
- <p>No blog has been created by <strong>{{ user.username }}</strong>yet.</p>
+ <p>No blog has been created by <strong>{{ user.username }}</strong> yet.</p>
{% endif %}
{% endif %}
<br/>
diff --git a/mediagoblin/media_types/blog/views.py b/mediagoblin/media_types/blog/views.py
index d48cf82f..288a47ae 100644
--- a/mediagoblin/media_types/blog/views.py
+++ b/mediagoblin/media_types/blog/views.py
@@ -376,7 +376,9 @@ def blog_about_view(request):
user = request.db.LocalUser.query.filter(
LocalUser.username==url_user
).first()
- blog = get_blog_by_slug(request, blog_slug, author=user.id)
+
+ if user:
+ blog = get_blog_by_slug(request, blog_slug, author=user.id)
if not user or not blog:
return render_404(request)
diff --git a/mediagoblin/media_types/video/models.py b/mediagoblin/media_types/video/models.py
index 4742b342..da635ed7 100644
--- a/mediagoblin/media_types/video/models.py
+++ b/mediagoblin/media_types/video/models.py
@@ -69,7 +69,7 @@ class VideoData(Base):
orig_metadata = self.orig_metadata or {}
if ("webm_video" not in self.get_media_entry.media_files
- and "mimetype" in orig_metadata['common']['tags']
+ and "mimetype" in orig_metadata.get('common', {}).get('tags', {})
and "codec" in orig_metadata['audio']
and "codec" in orig_metadata['video']):
if orig_metadata['mimetype'] == 'application/ogg':
diff --git a/mediagoblin/media_types/video/processing.py b/mediagoblin/media_types/video/processing.py
index ca3087a2..71204fc7 100644
--- a/mediagoblin/media_types/video/processing.py
+++ b/mediagoblin/media_types/video/processing.py
@@ -79,7 +79,17 @@ def sniffer(media_file):
return MEDIA_TYPE
+EXCLUDED_EXTS = ["nef", "svg"]
+
def sniff_handler(media_file, filename):
+ name, ext = os.path.splitext(filename)
+ clean_ext = ext.lower()[1:]
+
+ if clean_ext in EXCLUDED_EXTS:
+ # We don't handle this filetype, though gstreamer might think we can
+ _log.info('Refused to process {0} due to excluded extension'.format(filename))
+ return None
+
try:
return sniffer(media_file)
except:
@@ -106,10 +116,13 @@ def get_tags(stream_info):
# TODO: handle timezone info; gst.get_time_zone_offset +
# python's tzinfo should help
dt = tags['datetime']
- tags['datetime'] = datetime.datetime(
- dt.get_year(), dt.get_month(), dt.get_day(), dt.get_hour(),
- dt.get_minute(), dt.get_second(),
- dt.get_microsecond()).isoformat()
+ try:
+ tags['datetime'] = datetime.datetime(
+ dt.get_year(), dt.get_month(), dt.get_day(), dt.get_hour(),
+ dt.get_minute(), dt.get_second(),
+ dt.get_microsecond()).isoformat()
+ except:
+ tags['datetime'] = None
for k, v in tags.copy().items():
# types below are accepted by json; others must not present
if not isinstance(v, (dict, list, six.string_types, int, float, bool,
diff --git a/mediagoblin/media_types/video/transcoders.py b/mediagoblin/media_types/video/transcoders.py
index f4b0341e..2d3392f2 100644
--- a/mediagoblin/media_types/video/transcoders.py
+++ b/mediagoblin/media_types/video/transcoders.py
@@ -31,7 +31,7 @@ sys.argv = []
import gi
gi.require_version('Gst', '1.0')
-from gi.repository import GObject, Gst
+from gi.repository import GLib, Gst
Gst.init(None)
# init before import to work around https://bugzilla.gnome.org/show_bug.cgi?id=736260
from gi.repository import GstPbutils
@@ -154,7 +154,7 @@ class VideoTranscoder(object):
def __init__(self):
_log.info('Initializing VideoTranscoder...')
self.progress_percentage = None
- self.loop = GObject.MainLoop()
+ self.loop = GLib.MainLoop()
def transcode(self, src, dst, **kwargs):
'''
@@ -371,11 +371,11 @@ class VideoTranscoder(object):
self.pipeline.set_state(Gst.State.NULL)
# This kills the loop, mercifully
- GObject.idle_add(self.__stop_mainloop)
+ GLib.idle_add(self.__stop_mainloop)
def __stop_mainloop(self):
'''
- Wrapper for GObject.MainLoop.quit()
+ Wrapper for GLib.MainLoop.quit()
This wrapper makes us able to see if self.loop.quit has been called
'''
diff --git a/mediagoblin/plugins/api/views.py b/mediagoblin/plugins/api/views.py
index 23341065..fdd22ace 100644
--- a/mediagoblin/plugins/api/views.py
+++ b/mediagoblin/plugins/api/views.py
@@ -52,8 +52,6 @@ def post_entry(request):
_log.debug('File field not found')
raise BadRequest()
- upload_limit, max_file_size = get_upload_file_limits(request.user)
-
callback_url = request.form.get('callback_url')
if callback_url:
callback_url = six.text_type(callback_url)
@@ -66,7 +64,6 @@ def post_entry(request):
description=six.text_type(request.form.get('description')),
license=six.text_type(request.form.get('license', '')),
tags_string=six.text_type(request.form.get('tags', '')),
- upload_limit=upload_limit, max_file_size=max_file_size,
callback_url=callback_url)
return json_response(get_entry_serializable(entry, request.urlgen))
diff --git a/mediagoblin/plugins/basic_auth/README.rst b/mediagoblin/plugins/basic_auth/README.rst
index 82f247ed..87a7b16f 100644
--- a/mediagoblin/plugins/basic_auth/README.rst
+++ b/mediagoblin/plugins/basic_auth/README.rst
@@ -5,7 +5,7 @@
===================
The basic_auth plugin is enabled by default in mediagoblin.ini. This plugin
-provides basic username and password authentication for GNU Mediagoblin.
+provides basic username and password authentication for GNU MediaGoblin.
This plugin can be enabled alongside :ref:`openid-chapter` and
:ref:`persona-chapter`.
diff --git a/mediagoblin/plugins/basic_auth/forms.py b/mediagoblin/plugins/basic_auth/forms.py
index 9a6db226..3d684e91 100644
--- a/mediagoblin/plugins/basic_auth/forms.py
+++ b/mediagoblin/plugins/basic_auth/forms.py
@@ -38,7 +38,7 @@ class LoginForm(wtforms.Form):
username = wtforms.StringField(
_('Username or Email'),
[wtforms.validators.InputRequired(),
- normalize_user_or_email_field()])
+ normalize_user_or_email_field(is_login=True)])
password = wtforms.PasswordField(
_('Password'))
stay_logged_in = wtforms.BooleanField(
diff --git a/mediagoblin/plugins/flatpagesfile/README.rst b/mediagoblin/plugins/flatpagesfile/README.rst
index 59cd6217..0354a46c 100644
--- a/mediagoblin/plugins/flatpagesfile/README.rst
+++ b/mediagoblin/plugins/flatpagesfile/README.rst
@@ -57,7 +57,7 @@ Examples: ``flatpages-about``, ``about-view``, ``contact-view``, ...
The value has two parts separated by commas:
-1. **route path**: This is the url that this route matches.
+1. **route path**: This is the URL that this route matches.
Examples: ``/about``, ``/contact``, ``/pages/about``, ...
@@ -74,7 +74,7 @@ The value has two parts separated by commas:
For example: ``'/siteadmin/{adminname:\w+}'``
-2. **template**: The template to use for this url. The template is in
+2. **template**: The template to use for this URL. The template is in
the flatpagesfile template directory, so you just need to specify
the file name.
@@ -139,10 +139,10 @@ template::
Recipes
=======
-Url variables
+URL variables
-------------
-You can handle urls like ``/about/{name}`` and access the name that's
+You can handle URLs like ``/about/{name}`` and access the name that's
passed in in the template.
Sample route::
diff --git a/mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html b/mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html
index 87f790d1..be608ac0 100644
--- a/mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html
+++ b/mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html
@@ -43,8 +43,9 @@
href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>
contributors
</li><li>Imaging &copy;<a
- href="http://mapquest.com">MapQuest</a></li><li>Maps powered by
- <a href="http://leafletjs.com/"> Leaflet</a></li></ul>
+ href="https://www.openstreetmap.org">OpenStreetMap
+ contributors</a></li><li>Maps powered by
+ <a href="http://leafletjs.com/">Leaflet</a></li></ul>
</div>
<p>
<small>
diff --git a/mediagoblin/plugins/ldap/README.rst b/mediagoblin/plugins/ldap/README.rst
index ea9a34b3..049b5c4d 100644
--- a/mediagoblin/plugins/ldap/README.rst
+++ b/mediagoblin/plugins/ldap/README.rst
@@ -14,16 +14,16 @@
.. _ldap-plugin:
=============
- ldap plugin
+ LDAP plugin
=============
.. Warning::
This plugin is not compatible with the other authentication plugins.
-This plugin allow your GNU Mediagoblin instance to authenticate against an
+This plugin allow your GNU MediaGoblin instance to authenticate against an
LDAP server.
-Set up the ldap plugin
+Set up the LDAP plugin
======================
1. Install the ``python-ldap`` package.
@@ -32,13 +32,13 @@ Set up the ldap plugin
[[mediagoblin.plugins.ldap]]
-Configuring the ldap plugin
+Configuring the LDAP plugin
===========================
-This plugin allows you to use multiple ldap servers for authentication.
+This plugin allows you to use multiple LDAP servers for authentication.
In order to configure a server, add the following to you MediaGoblin .ini file
-under the ldap plugin::
+under the LDAP plugin::
[[mediagoblin.plugins.ldap]]
[[[server1]]]
@@ -50,15 +50,15 @@ under the ldap plugin::
Make any necessary changes to the above to work with your sever. Make sure
``{username}`` is where the username should be in LDAP_USER_DN_TEMPLATE.
-If you would like to fetch the users email from the ldap server upon account
+If you would like to fetch the users email from the LDAP server upon account
registration, add ``LDAP_SEARCH_BASE = 'ou=users,dc=testathon,dc=net'`` and
``EMAIL_SEARCH_FIELD = 'mail'`` under you server configuration in your
MediaGoblin .ini file.
.. Warning::
By default, this plugin provides no encryption when communicating with the
- ldap servers. If you would like to use an SSL connection, change
- LDAP_SERVER_URI to use ``ldaps://`` and whichever port you use. Default ldap
+ LDAP servers. If you would like to use an SSL connection, change
+ LDAP_SERVER_URI to use ``ldaps://`` and whichever port you use. Default LDAP
port for SSL connections is 636. If you would like to use a TLS connection,
add ``LDAP_START_TLS = 'true'`` under your server configuration in your
MediaGoblin .ini file.
diff --git a/mediagoblin/plugins/metadata_display/templates/mediagoblin/plugins/metadata_display/metadata_table.html.orig b/mediagoblin/plugins/metadata_display/templates/mediagoblin/plugins/metadata_display/metadata_table.html.orig
deleted file mode 100644
index 2bd1a14c..00000000
--- a/mediagoblin/plugins/metadata_display/templates/mediagoblin/plugins/metadata_display/metadata_table.html.orig
+++ /dev/null
@@ -1,60 +0,0 @@
-{#
-# 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/>.
-#}
-
-<<<<<<< HEAD:mediagoblin/templates/mediagoblin/utils/metadata_table.html
-{%- macro render_table(request, media_entry, format_predicate) %}
- {%- set metadata=media_entry.media_metadata %}
- {%- set metadata_context=metadata['@context'] %}
- {%- if metadata %}
- <h3>{% trans %}Metadata Information{% endtrans %}</h3>
- <table class="metadata_info">
- {%- for key, value in metadata.iteritems() if (
- not key=='@context' and value) %}
- <tr {% if loop.index%2 == 1 %}class="highlight"{% endif %}>
- <th>{{ format_predicate(key) }}</th>
- <td property="{{ key }}">
- {{ value }}</td>
- </tr>
- {%- endfor %}
- </table>
- {% endif %}
- {% if request.user and request.user.has_privilege('admin') %}
- <a href="{{ request.urlgen('mediagoblin.edit.metadata',
- user=media_entry.get_uploader.username,
- media_id=media_entry.id) }}">
- {% trans %}Edit Metadata{% endtrans %}</a>
- {% endif %}
-{%- endmacro %}
-=======
-{%- set metadata=media.media_metadata %}
-{%- set metadata_context=metadata['@context'] %}
-{%- if metadata %}
- {#- NOTE: In some smart future where the context is more extensible,
- we will need to add to the prefix here-#}
- <table>
- {%- for key, value in metadata.iteritems() if not key=='@context' %}
- {% if value -%}
- <tr>
- <td>{{ rdfa_to_readable(key) }}</td>
- <td property="{{ key }}">{{ value }}</td>
- </tr>
- {%- endif -%}
- {%- endfor %}
- </table>
-{% endif %}
->>>>>>> acfcaf6366bd4695c1c37c7aa8ff5a176b412e2a:mediagoblin/plugins/metadata_display/templates/mediagoblin/plugins/metadata_display/metadata_table.html
diff --git a/mediagoblin/plugins/openid/README.rst b/mediagoblin/plugins/openid/README.rst
index 870a2b58..1a777336 100644
--- a/mediagoblin/plugins/openid/README.rst
+++ b/mediagoblin/plugins/openid/README.rst
@@ -1,23 +1,23 @@
.. _openid-chapter:
===================
- openid plugin
+ OpenID plugin
===================
-The openid plugin allows user to login to your GNU Mediagoblin instance using
-their openid url.
+The OpenID plugin allows user to login to your GNU MediaGoblin instance using
+their OpenID URL.
This plugin can be enabled alongside :ref:`basic_auth-chapter` and
:ref:`persona-chapter`.
.. note::
- When :ref:`basic_auth-chapter` is enabled alongside this openid plugin, and
- a user creates an account using their openid. If they would like to add a
+ When :ref:`basic_auth-chapter` is enabled alongside this OpenID plugin, and
+ a user creates an account using their OpenID. If they would like to add a
password to their account, they can use the forgot password feature to do
so.
-Set up the openid plugin
+Set up the OpenID plugin
============================
1. Install the ``python-openid`` package.
diff --git a/mediagoblin/plugins/piwigo/views.py b/mediagoblin/plugins/piwigo/views.py
index ab741a72..30c7ffa2 100644
--- a/mediagoblin/plugins/piwigo/views.py
+++ b/mediagoblin/plugins/piwigo/views.py
@@ -128,16 +128,13 @@ def pwg_images_addSimple(request):
if not check_file_field(request, 'image'):
raise BadRequest()
- upload_limit, max_file_size = get_upload_file_limits(request.user)
-
try:
entry = submit_media(
mg_app=request.app, user=request.user,
submitted_file=request.files['image'],
filename=request.files['image'].filename,
title=six.text_type(form.name.data),
- description=six.text_type(form.comment.data),
- upload_limit=upload_limit, max_file_size=max_file_size)
+ description=six.text_type(form.comment.data))
collection_id = form.category.data
if collection_id > 0:
diff --git a/mediagoblin/plugins/trim_whitespace/README.rst b/mediagoblin/plugins/trim_whitespace/README.rst
index db9a0c53..d83af06b 100644
--- a/mediagoblin/plugins/trim_whitespace/README.rst
+++ b/mediagoblin/plugins/trim_whitespace/README.rst
@@ -2,7 +2,7 @@
Trim whitespace plugin
=======================
-Mediagoblin templates are written with 80 char limit for better
+MediaGoblin templates are written with 80 char limit for better
readability. However that means that the HTML output is very verbose
containing *lots* of whitespace. This plugin inserts a middleware that
filters out whitespace from the returned HTML in the ``Response()``
diff --git a/mediagoblin/processing/task.py b/mediagoblin/processing/task.py
index 5e0e772d..bedfd32d 100644
--- a/mediagoblin/processing/task.py
+++ b/mediagoblin/processing/task.py
@@ -69,6 +69,9 @@ class ProcessMedia(celery.Task):
"""
Pass this entry off for processing.
"""
+
+ name = 'process_media'
+
def run(self, media_id, feed_url, reprocess_action, reprocess_info=None):
"""
Pass the media entry off to the appropriate processing function
diff --git a/mediagoblin/static/css/audio.css b/mediagoblin/static/css/audio.css
index 5c50e727..de388094 100644
--- a/mediagoblin/static/css/audio.css
+++ b/mediagoblin/static/css/audio.css
@@ -24,10 +24,11 @@
font-size: 40px;
width: 50px;
text-shadow: 0 0 10px black;
+ background: none;
+ border: none;
}
.audio-control-play-pause.playing {
color: #b71500;
- letter-spacing: -17px;
margin-left: -7px;
}
.audio-control-play-pause.paused {
diff --git a/mediagoblin/static/css/base.css b/mediagoblin/static/css/base.css
index 7852cae9..6da19f94 100644
--- a/mediagoblin/static/css/base.css
+++ b/mediagoblin/static/css/base.css
@@ -394,6 +394,12 @@ text-align: center;
margin-right: auto;
}
+.form_box > h1, .form_box_xl > h1 {
+ /* Fix header overflowing issue. */
+ overflow: hidden;
+ text-overflow: ellipsis
+}
+
.form_box_xl {
max-width: 460px;
}
@@ -457,11 +463,9 @@ text-align: center;
}
.form_field_label {
- margin-bottom: 4px;
-}
-
-.form_field_label {
font-size:1.125em;
+ margin-bottom: 0;
+ padding: 10px 0;
}
.form_field_description {
@@ -585,7 +589,6 @@ ul#action_to_resolve {list-style:none; margin-left:10px;}
border-radius: 0 0 5px 5px;
padding: 0 0 6px;
text-overflow: ellipsis;
- white-space: nowrap;
overflow: hidden;
border-color: #0D0D0D;
border-style: solid;
diff --git a/mediagoblin/static/js/audio.js b/mediagoblin/static/js/audio.js
index 50d58cd9..59a8c801 100644
--- a/mediagoblin/static/js/audio.js
+++ b/mediagoblin/static/js/audio.js
@@ -116,6 +116,10 @@ var audioPlayer = new Object();
var im = audioPlayer.imageElement;
var pos = (e.offsetX || e.originalEvent.layerX) / im.width();
+ console.log('pos', (e.offsetX || e.originalEvent.layerX) / im.width())
+ console.log('setting current time to',
+ pos * audioPlayer.audioElement.duration)
+
audioPlayer.audioElement.currentTime = pos * audioPlayer.audioElement.duration;
audioPlayer.audioElement.play();
audioPlayer.setState(audioPlayer.PLAYING);
@@ -151,14 +155,16 @@ var audioPlayer = new Object();
switch (state) {
case audioPlayer.PLAYING:
- $('.audio-spectrogram .audio-control-play-pause')
+ el = $('.audio-spectrogram .audio-control-play-pause')
.removeClass('paused').addClass('playing')
- .text('▮▮');
+ .text('▮▮').attr('aria-label', 'Pause');
+ el[0].setAttribute('aria-label', 'Pause')
break;
case audioPlayer.PAUSED:
- $('.audio-spectrogram .audio-control-play-pause')
+ el = $('.audio-spectrogram .audio-control-play-pause')
.removeClass('playing').addClass('paused')
- .text('▶');
+ .text('▶').attr('aria-label', 'Play');
+ el[0].setAttribute('aria-label', 'Play')
break;
}
};
@@ -200,19 +206,9 @@ var audioPlayer = new Object();
* Attach the player to an image element
*/
console.log(imageElement);
-
var im = $(imageElement);
-
audioPlayer.imageElement = im;
- $('<div class="playhead"></div>').appendTo(im.parent());
- $('<div class="buffered-indicators"></div>').appendTo(im.parent());
- $('<div class="seekbar"></div>').appendTo(im.parent());
- $('<div class="audio-control-play-pause paused">▶</div>').appendTo(im.parent());
- $('<div class="audio-currentTime">00:00</div>').appendTo(im.parent());
- $('<input type="range" class="audio-volume"'
- +'value="1" min="0" max="1" step="0.001" />').appendTo(im.parent());
- $('.audio-spectrogram').trigger('attachedControls');
};
})(audioPlayer);
diff --git a/mediagoblin/static/js/geolocation-map.js b/mediagoblin/static/js/geolocation-map.js
index 26d94c5d..c30788f7 100644
--- a/mediagoblin/static/js/geolocation-map.js
+++ b/mediagoblin/static/js/geolocation-map.js
@@ -30,13 +30,11 @@ $(document).ready(function () {
// Get a new map instance attached and element with id="tile-map"
var map = new L.Map('tile-map');
- var mqtileUrl = 'http://otile{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg';
+ var mqtileUrl = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
var mqtileAttrib = '<a id="osm_license_link">see map license</a>';
var mqtile = new L.TileLayer(
mqtileUrl,
- {maxZoom: 18,
- attribution: mqtileAttrib,
- subdomains: '1234'});
+ {maxZoom: 18});
map.attributionControl.setPrefix('');
var location = new L.LatLng(latitude, longitude);
diff --git a/mediagoblin/static/js/header_dropdown.js b/mediagoblin/static/js/header_dropdown.js
index 3ee46228..979d2690 100644
--- a/mediagoblin/static/js/header_dropdown.js
+++ b/mediagoblin/static/js/header_dropdown.js
@@ -17,9 +17,27 @@
*/
$(document).ready(function(){
- $("#header_dropdown").hide();
- $(".header_dropdown_up").hide();
- $(".header_dropdown_down,.header_dropdown_up").click(function() {
+ // The header drop-down header panel defaults to open until you explicitly
+ // close it. After that, the panel open/closed setting will persist across
+ // page loads.
+
+ // Initialise the panel status when page is loaded.
+ if (localStorage.getItem("panel_closed")) {
+ $("#header_dropdown").hide();
+ $(".header_dropdown_up").hide();
+ }
+ else {
+ $(".header_dropdown_down").hide();
+ }
+
+ // Toggle and persist the panel status.
+ $(".header_dropdown_down, .header_dropdown_up").click(function() {
+ if (localStorage.getItem("panel_closed")) {
+ localStorage.removeItem("panel_closed");
+ }
+ else {
+ localStorage.setItem("panel_closed", "true");
+ }
$(".header_dropdown_down").toggle();
$(".header_dropdown_up").toggle();
$("#header_dropdown").slideToggle();
diff --git a/mediagoblin/static/js/post_comment.js b/mediagoblin/static/js/post_comment.js
new file mode 100644
index 00000000..431c222f
--- /dev/null
+++ b/mediagoblin/static/js/post_comment.js
@@ -0,0 +1,63 @@
+/**
+ * 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(){
+ $(function() {
+ // Hide this button if script is enabled
+ $('.form_submit_buttons').find('input').hide();
+
+ // Include this link if script is enabled
+ $('.form_submit_buttons').append(
+ '<a class="button_action" id="post_comment" type="button">' +
+ 'Add this comment </a>');
+
+ $('#post_comment').click(function() {
+ $.ajax({
+ url: $('#postCommentURL').val(),
+ data: $('#form_comment').serialize(),
+ type: 'POST',
+ success: function(response) {
+ var message = $(response).find('.mediagoblin_messages');
+ var commentsInResponse = $($(response).find('.media_comments')).find('li');
+ var commentsInPage = $('.media_comments').find('ul');
+
+ // Post the message
+ message.css({"position":"fixed", "top":"50px", "width":"100%"});
+ $('body').append(message);
+ message.delay(1500).fadeOut();
+
+ // Checking if there is new comment
+ if(commentsInResponse.length != $(commentsInPage).find('li').length) {
+ // Post comment and scroll down to it
+ var newComment = commentsInResponse[commentsInResponse.length - 1];
+ $('#form_comment').fadeOut('fast');
+ $('#button_addcomment').fadeIn('fast');
+ $('#comment_preview').replaceWith("<div id=comment_preview></div>");
+ $(commentsInPage).append(newComment);
+ $('html, body').animate({
+ scrollTop: $(newComment).offset().top
+ }, 1000);
+ }
+ },
+ error: function(error) {
+ console.log(error);
+ }
+ });
+ });
+ });
+}); \ No newline at end of file
diff --git a/mediagoblin/static/js/show_password.js b/mediagoblin/static/js/show_password.js
index b3fbc862..12935124 100644
--- a/mediagoblin/static/js/show_password.js
+++ b/mediagoblin/static/js/show_password.js
@@ -18,7 +18,7 @@
$(document).ready(function(){
//Create a duplicate password field. We could change the input type dynamically, but this angers the IE gods (not just IE6).
- $("#password").after('<input type="text" value="" name="password_clear" id="password_clear" /><label><input type="checkbox" id="password_boolean" />Show password</label>');
+ $("#password").after('<input type="text" value="" name="password_clear" id="password_clear" style="width:100%" /><label><br/><input type="checkbox" id="password_boolean" />Show password</label>');
$('#password_clear').hide();
$('#password_boolean').click(function(){
if($('#password_boolean').prop("checked")) {
diff --git a/mediagoblin/submit/lib.py b/mediagoblin/submit/lib.py
index 2edea70f..08a603e9 100644
--- a/mediagoblin/submit/lib.py
+++ b/mediagoblin/submit/lib.py
@@ -27,11 +27,12 @@ from mediagoblin import mg_globals
from mediagoblin.tools.response import json_response
from mediagoblin.tools.text import convert_to_tag_list_of_dicts
from mediagoblin.tools.federation import create_activity, create_generator
-from mediagoblin.db.models import MediaEntry, ProcessingMetaData
+from mediagoblin.db.models import Collection, MediaEntry, ProcessingMetaData
from mediagoblin.processing import mark_entry_failed
from mediagoblin.processing.task import ProcessMedia
from mediagoblin.notifications import add_comment_subscription
from mediagoblin.media_types import sniff_media
+from mediagoblin.user_pages.lib import add_media_to_collection
_log = logging.getLogger(__name__)
@@ -101,9 +102,8 @@ class UserPastUploadLimit(UploadLimitError):
def submit_media(mg_app, user, submitted_file, filename,
- title=None, description=None,
+ title=None, description=None, collection_slug=None,
license=None, metadata=None, tags_string=u"",
- upload_limit=None, max_file_size=None,
callback_url=None, urlgen=None,):
"""
Args:
@@ -116,15 +116,15 @@ def submit_media(mg_app, user, submitted_file, filename,
one on disk being referenced by submitted_file.
- title: title for this media entry
- description: description for this media entry
+ - collection_slug: collection for this media entry
- license: license for this media entry
- tags_string: comma separated string of tags to be associated
with this entry
- - upload_limit: size in megabytes that's the per-user upload limit
- - max_file_size: maximum size each file can be that's uploaded
- callback_url: possible post-hook to call after submission
- urlgen: if provided, used to do the feed_url update and assign a public
ID used in the API (very important).
"""
+ upload_limit, max_file_size = get_upload_file_limits(user)
if upload_limit and user.uploaded >= upload_limit:
raise UserPastUploadLimit()
@@ -205,6 +205,13 @@ def submit_media(mg_app, user, submitted_file, filename,
create_activity("post", entry, entry.actor)
entry.save()
+ # add to collection
+ if collection_slug:
+ collection = Collection.query.filter_by(slug=collection_slug,
+ actor=user.id).first()
+ if collection:
+ add_media_to_collection(collection, entry)
+
# Pass off to processing
#
# (... don't change entry after this point to avoid race
diff --git a/mediagoblin/submit/views.py b/mediagoblin/submit/views.py
index be473615..7bbfb645 100644
--- a/mediagoblin/submit/views.py
+++ b/mediagoblin/submit/views.py
@@ -76,7 +76,6 @@ def submit_start(request):
description=six.text_type(submit_form.description.data),
license=six.text_type(submit_form.license.data) or None,
tags_string=submit_form.tags.data,
- upload_limit=upload_limit, max_file_size=max_file_size,
urlgen=request.urlgen)
if submit_form.collection and submit_form.collection.data:
diff --git a/mediagoblin/templates/mediagoblin/federation/host-meta.xml b/mediagoblin/templates/mediagoblin/api/host-meta.xml
index 7970a0d2..7970a0d2 100644
--- a/mediagoblin/templates/mediagoblin/federation/host-meta.xml
+++ b/mediagoblin/templates/mediagoblin/api/host-meta.xml
diff --git a/mediagoblin/templates/mediagoblin/api/oob.html b/mediagoblin/templates/mediagoblin/api/oob.html
index 97bdd9ca..e4d3a132 100644
--- a/mediagoblin/templates/mediagoblin/api/oob.html
+++ b/mediagoblin/templates/mediagoblin/api/oob.html
@@ -27,7 +27,5 @@
<h4>{% trans %}Copy and paste this <strong>verifier code</strong> into your client:{% endtrans %}</h4>
-<p class="verifier">
- {{ oauth_request.verifier }}
-</p>
+<p class="verifier">{{ oauth_request.verifier }}</p>
{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/auth/register.html b/mediagoblin/templates/mediagoblin/auth/register.html
index a7b8033f..b52ecff4 100644
--- a/mediagoblin/templates/mediagoblin/auth/register.html
+++ b/mediagoblin/templates/mediagoblin/auth/register.html
@@ -37,6 +37,7 @@
{% template_hook("register_link") %}
{{ wtforms_util.render_divs(register_form, True) }}
{{ csrf_token }}
+ {% template_hook("register_captcha") %}
<div class="form_submit_buttons">
<input type="submit" value="{% trans %}Create{% endtrans %}"
class="button_form" />
diff --git a/mediagoblin/templates/mediagoblin/media_displays/audio.html b/mediagoblin/templates/mediagoblin/media_displays/audio.html
index 7571f863..191eff14 100644
--- a/mediagoblin/templates/mediagoblin/media_displays/audio.html
+++ b/mediagoblin/templates/mediagoblin/media_displays/audio.html
@@ -30,6 +30,12 @@
<div class="audio-media">
{% if 'spectrogram' in media.media_files %}
<div class="audio-spectrogram">
+ <div class="playhead"></div>
+ <div class="buffered-indicators"></div>
+ <div class="seekbar"></div>
+ <button class="audio-control-play-pause paused" aria-label="Play">▶</button>
+ <div class="audio-currentTime" aria-label="current time">00:00</div>
+ <input type="range" class="audio-volume" value="1" min="0" max="1" step="0.001" aria-label="volume" />
<img src="{{ request.app.public_store.file_url(
media.media_files.spectrogram) }}"
alt="Spectrogram" />
diff --git a/mediagoblin/templates/mediagoblin/moderation/media_panel.html b/mediagoblin/templates/mediagoblin/moderation/media_panel.html
index 94d4a1a0..e60f5e98 100644
--- a/mediagoblin/templates/mediagoblin/moderation/media_panel.html
+++ b/mediagoblin/templates/mediagoblin/moderation/media_panel.html
@@ -35,6 +35,7 @@
{% if processing_entries.count() %}
<table class="media_panel processing">
<tr>
+ <th>{% trans %}Thumbnail{% endtrans %}</th>
<th>{% trans %}ID{% endtrans %}</th>
<th>{% trans %}User{% endtrans %}</th>
<th>{% trans %}Title{% endtrans %}</th>
diff --git a/mediagoblin/templates/mediagoblin/user_pages/blog_media.html b/mediagoblin/templates/mediagoblin/user_pages/blog_media.html
index 261b21e7..24328725 100644
--- a/mediagoblin/templates/mediagoblin/user_pages/blog_media.html
+++ b/mediagoblin/templates/mediagoblin/user_pages/blog_media.html
@@ -77,7 +77,7 @@
{% set delete_url = request.urlgen('mediagoblin.user_pages.media_confirm_delete',
user= media.get_actor.username,
media_id=media.id) %}
- <a class="button_action" href="{{ delete_url }}">{% trans %}Delete{% endtrans %}</a>
+ <a class="button_action button_warning" href="{{ delete_url }}">{% trans %}Delete{% endtrans %}</a>
{% endif %}
</br>
@@ -165,8 +165,6 @@
{% include "mediagoblin/utils/license.html" %}
- {% include "mediagoblin/utils/exif.html" %}
-
{% template_hook("media_sideinfo") %}
{% block mediagoblin_sidebar %}
diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html
index d1f437d1..b93da06e 100644
--- a/mediagoblin/templates/mediagoblin/user_pages/media.html
+++ b/mediagoblin/templates/mediagoblin/user_pages/media.html
@@ -29,6 +29,8 @@
src="{{ request.staticdirect('/js/comment_show.js') }}"></script>
<script type="text/javascript"
src="{{ request.staticdirect('/js/keyboard_navigation.js') }}"></script>
+ <script type="text/javascript"
+ src="{{ request.staticdirect('/js/post_comment.js') }}"></script>
{% template_hook("location_head") %}
{% template_hook("media_head") %}
@@ -75,6 +77,7 @@
<h2 class="media_title">
{{ media.title }}
</h2>
+ {% template_hook("media_titleinfo") %}
{% if request.user and
(media.actor == request.user.id or
request.user.has_privilege('admin')) %}
@@ -116,6 +119,7 @@
<input type="submit" value="{% trans %}Add this comment{% endtrans %}" class="button_action" />
{{ csrf_token }}
</div>
+ <input type="hidden" value="{{ request.urlgen('mediagoblin.user_pages.media_post_comment', user= media.get_actor.username, media_id=media.id) }}" id="postCommentURL" />
<input type="hidden" value="{{ request.urlgen('mediagoblin.user_pages.media_preview_comment') }}" id="previewURL" />
<input type="hidden" value="{% trans %}Comment Preview{% endtrans %}" id="previewText"/>
</form>
diff --git a/mediagoblin/templates/mediagoblin/user_pages/processing_panel.html b/mediagoblin/templates/mediagoblin/user_pages/processing_panel.html
index 96786937..ee7b646a 100644
--- a/mediagoblin/templates/mediagoblin/user_pages/processing_panel.html
+++ b/mediagoblin/templates/mediagoblin/user_pages/processing_panel.html
@@ -40,7 +40,7 @@ Show:
<a href="{{ request.urlgen('mediagoblin.user_pages.processing_panel',
user=request.user.username, state="failed") }}">Failed</a>,
<a href="{{ request.urlgen('mediagoblin.user_pages.processing_panel',
- user=request.user.username, state="processed") }}">Succesful</a>
+ user=request.user.username, state="processed") }}">Successful</a>
</p>
{% if entries.count() %}
diff --git a/mediagoblin/templates/mediagoblin/utils/collection_gallery.html b/mediagoblin/templates/mediagoblin/utils/collection_gallery.html
index 86680cb6..8a3f7a75 100644
--- a/mediagoblin/templates/mediagoblin/utils/collection_gallery.html
+++ b/mediagoblin/templates/mediagoblin/utils/collection_gallery.html
@@ -31,11 +31,17 @@
{%- if loop.first %} thumb_entry_first
{%- elif loop.last %} thumb_entry_last{% endif %}">
<a href="{{ obj_url }}">
+ {% if obj.icon_url %}
+ <img class="entry_type_icon" src="{{ obj.icon_url }}" />
+ {% endif %}
<img src="{{ obj.thumb_url }}" />
</a>
+ {% if obj.title %}
+ <a href="{{ obj_url }}">{{ obj.title }}</a>
+ {% endif %}
{% if item.note %}
- <a href="{{ obj_url }}">{{ item.note }}</a>
+ {{ item.note }}
{% endif %}
{% if request.user and
(item.in_collection.actor == request.user.id or
diff --git a/mediagoblin/templates/mediagoblin/utils/prev_next.html b/mediagoblin/templates/mediagoblin/utils/prev_next.html
index 9e262ed9..fc8672fb 100644
--- a/mediagoblin/templates/mediagoblin/utils/prev_next.html
+++ b/mediagoblin/templates/mediagoblin/utils/prev_next.html
@@ -19,29 +19,36 @@
{# Provide navigation links to neighboring media entries, if possible #}
{% set prev_entry_url = media.url_to_prev(request.urlgen) %}
{% set next_entry_url = media.url_to_next(request.urlgen) %}
+{% if is_rtl %}
+ {% set next_arrow = "→" %}
+ {% set prev_arrow = "←" %}
+{% else %}
+ {% set next_arrow = "←" %}
+ {% set prev_arrow = "→" %}
+{% endif %}
{% if prev_entry_url or next_entry_url %}
<div class="navigation">
{# There are no previous entries for the very first media entry #}
{% if prev_entry_url %}
<a class="navigation_button navigation_left" href="{{ prev_entry_url }}">
- &larr; {% trans %}newer{% endtrans %}
+ {{next_arrow}} {% trans %}newer{% endtrans %}
</a>
{% else %}
{# This is the first entry. display greyed-out 'previous' image #}
<p class="navigation_button navigation_left">
- &larr; {% trans %}newer{% endtrans %}
+ {{next_arrow}} {% trans %}newer{% endtrans %}
</p>
{% endif %}
{# Likewise, this could be the very last media entry #}
{% if next_entry_url %}
<a class="navigation_button navigation_right" href="{{ next_entry_url }}">
- {% trans %}older{% endtrans %} &rarr;
+ {% trans %}older{% endtrans %} {{prev_arrow}}
</a>
{% else %}
{# This is the last entry. display greyed-out 'next' image #}
<p class="navigation_button navigation_right">
- {% trans %}older{% endtrans %} &rarr;
+ {% trans %}older{% endtrans %} {{prev_arrow}}
</p>
{% endif %}
</div>
diff --git a/mediagoblin/templates/mediagoblin/utils/wtforms.html b/mediagoblin/templates/mediagoblin/utils/wtforms.html
index 7e16708c..e2921258 100644
--- a/mediagoblin/templates/mediagoblin/utils/wtforms.html
+++ b/mediagoblin/templates/mediagoblin/utils/wtforms.html
@@ -40,9 +40,9 @@
{{- render_label_p(field) }}
<div class="form_field_input">
{% if autofocus_first %}
- {{ field(autofocus=True) }}
+ {{ field(autofocus=True, style="width:100%;") }}
{% else %}
- {{ field }}
+ {{ field(style="width:100%;") }}
{% endif %}
{%- if field.errors -%}
{% for error in field.errors %}
diff --git a/mediagoblin/tests/.gitignore b/mediagoblin/tests/.gitignore
new file mode 100644
index 00000000..16d3c4db
--- /dev/null
+++ b/mediagoblin/tests/.gitignore
@@ -0,0 +1 @@
+.cache
diff --git a/mediagoblin/tests/fake_carrot_conf_good.ini b/mediagoblin/tests/fake_carrot_conf_good.ini
index 1377907b..8dc32525 100644
--- a/mediagoblin/tests/fake_carrot_conf_good.ini
+++ b/mediagoblin/tests/fake_carrot_conf_good.ini
@@ -7,7 +7,7 @@ num_carrots = 88
encouragement_phrase = "I'd love it if you eat your carrots!"
# Something extra!
-blah_blah = "blah!"
+blah_blah = "blæh!"
[celery]
EAT_CELERY_WITH_CARROTS = False
diff --git a/mediagoblin/tests/resources.py b/mediagoblin/tests/resources.py
index 480f6d9a..38406d62 100644
--- a/mediagoblin/tests/resources.py
+++ b/mediagoblin/tests/resources.py
@@ -41,3 +41,4 @@ GOOD_JPG = resource_exif('good.jpg')
EMPTY_JPG = resource_exif('empty.jpg')
BAD_JPG = resource_exif('bad.jpg')
GPS_JPG = resource_exif('has-gps.jpg')
+BAD_GPS_JPG = resource_exif('bad-gps.jpg')
diff --git a/mediagoblin/tests/test_api.py b/mediagoblin/tests/test_api.py
index 90873cb9..f4741fd1 100644
--- a/mediagoblin/tests/test_api.py
+++ b/mediagoblin/tests/test_api.py
@@ -25,11 +25,11 @@ from webtest import AppError
from .resources import GOOD_JPG
from mediagoblin import mg_globals
-from mediagoblin.db.models import User, Activity, MediaEntry, TextComment
-from mediagoblin.tools.routing import extract_url_arguments
+from mediagoblin.db.models import User, MediaEntry, TextComment
from mediagoblin.tests.tools import fixture_add_user
from mediagoblin.moderation.tools import take_away_privileges
+
class TestAPI(object):
""" Test mediagoblin's pump.io complient APIs """
@@ -38,7 +38,8 @@ class TestAPI(object):
self.test_app = test_app
self.db = mg_globals.database
- self.user = fixture_add_user(privileges=[u'active', u'uploader', u'commenter'])
+ self.user = fixture_add_user(privileges=[u'active', u'uploader',
+ u'commenter'])
self.other_user = fixture_add_user(
username="otheruser",
privileges=[u'active', u'uploader', u'commenter']
@@ -61,7 +62,7 @@ class TestAPI(object):
return response, json.loads(response.body.decode())
- def _upload_image(self, test_app, image):
+ def _upload_image(self, test_app, image, custom_filename=None):
""" Uploads and image to MediaGoblin via pump.io API """
data = open(image, "rb").read()
headers = {
@@ -69,6 +70,8 @@ class TestAPI(object):
"Content-Length": str(len(data))
}
+ if custom_filename is not None:
+ headers["X-File-Name"] = custom_filename
with self.mock_oauth():
response = test_app.post(
@@ -126,9 +129,48 @@ class TestAPI(object):
assert image["objectType"] == "image"
# Check that we got the response we're expecting
- response, _ = self._post_image_to_feed(test_app, image)
+ response, data = self._post_image_to_feed(test_app, image)
+ assert response.status_code == 200
+ assert data["object"]["fullImage"]["url"].endswith("unknown.jpe")
+ assert data["object"]["image"]["url"].endswith("unknown.thumbnail.jpe")
+
+ def test_can_post_image_custom_filename(self, test_app):
+ """ Tests an image can be posted to the API with custom filename """
+ # First request we need to do is to upload the image
+ response, image = self._upload_image(test_app, GOOD_JPG,
+ custom_filename="hello.jpg")
+
+ # I should have got certain things back
+ assert response.status_code == 200
+
+ assert "id" in image
+ assert "fullImage" in image
+ assert "url" in image["fullImage"]
+ assert "url" in image
+ assert "author" in image
+ assert "published" in image
+ assert "updated" in image
+ assert image["objectType"] == "image"
+
+ # Check that we got the response we're expecting
+ response, data = self._post_image_to_feed(test_app, image)
+ assert response.status_code == 200
+ assert data["object"]["fullImage"]["url"].endswith("hello.jpg")
+ assert data["object"]["image"]["url"].endswith("hello.thumbnail.jpg")
+
+ def test_can_post_image_tags(self, test_app):
+ """ Tests that an image can be posted to the API """
+ # First request we need to do is to upload the image
+ response, image = self._upload_image(test_app, GOOD_JPG)
assert response.status_code == 200
+ image["tags"] = ["hello", "world"]
+
+ # Check that we got the response we're expecting
+ response, data = self._post_image_to_feed(test_app, image)
+ assert response.status_code == 200
+ assert data["object"]["tags"] == ["hello", "world"]
+
def test_unable_to_upload_as_someone_else(self, test_app):
""" Test that can't upload as someoen else """
data = open(GOOD_JPG, "rb").read()
@@ -172,7 +214,7 @@ class TestAPI(object):
assert "403 FORBIDDEN" in excinfo.value.args[0]
def test_only_able_to_update_own_image(self, test_app):
- """ Test's that the uploader is the only person who can update an image """
+ """ Test uploader is the only person who can update an image """
response, data = self._upload_image(test_app, GOOD_JPG)
response, data = self._post_image_to_feed(test_app, data)
@@ -186,13 +228,16 @@ class TestAPI(object):
}
# Lets change the image uploader to be self.other_user, this is easier
- # than uploading the image as someone else as the way self.mocked_oauth_required
- # and self._upload_image.
- media = MediaEntry.query.filter_by(public_id=data["object"]["id"]).first()
+ # than uploading the image as someone else as the way
+ # self.mocked_oauth_required and self._upload_image.
+ media = MediaEntry.query \
+ .filter_by(public_id=data["object"]["id"]) \
+ .first()
media.actor = self.other_user.id
media.save()
- # Now lets try and edit the image as self.user, this should produce a 403 error.
+ # Now lets try and edit the image as self.user, this should produce a
+ # 403 error.
with self.mock_oauth():
with pytest.raises(AppError) as excinfo:
test_app.post(
@@ -242,7 +287,6 @@ class TestAPI(object):
assert image["content"] == description
assert image["license"] == license
-
def test_only_uploaders_post_image(self, test_app):
""" Test that only uploaders can upload images """
# Remove uploader permissions from user
@@ -288,12 +332,15 @@ class TestAPI(object):
image = json.loads(request.body.decode())
entry = MediaEntry.query.filter_by(public_id=image["id"]).first()
+ assert entry is not None
+
assert request.status_code == 200
assert "image" in image
assert "fullImage" in image
assert "pump_io" in image
assert "links" in image
+ assert "tags" in image
def test_post_comment(self, test_app):
""" Tests that I can post an comment media """
@@ -316,7 +363,9 @@ class TestAPI(object):
assert response.status_code == 200
# Find the objects in the database
- media = MediaEntry.query.filter_by(public_id=data["object"]["id"]).first()
+ media = MediaEntry.query \
+ .filter_by(public_id=data["object"]["id"]) \
+ .first()
comment = media.get_comments()[0].comment()
# Tests that it matches in the database
@@ -378,7 +427,9 @@ class TestAPI(object):
response, comment_data = self._activity_to_feed(test_app, activity)
# change who uploaded the comment as it's easier than changing
- comment = TextComment.query.filter_by(public_id=comment_data["object"]["id"]).first()
+ comment = TextComment.query \
+ .filter_by(public_id=comment_data["object"]["id"]) \
+ .first()
comment.actor = self.other_user.id
comment.save()
@@ -432,7 +483,7 @@ class TestAPI(object):
def test_whoami_without_login(self, test_app):
""" Test that whoami endpoint returns error when not logged in """
with pytest.raises(AppError) as excinfo:
- response = test_app.get("/api/whoami")
+ test_app.get("/api/whoami")
assert "401 UNAUTHORIZED" in excinfo.value.args[0]
@@ -621,8 +672,11 @@ class TestAPI(object):
delete = self._activity_to_feed(test_app, activity)[1]
# Verify the comment no longer exists
- assert TextComment.query.filter_by(public_id=comment["object"]["id"]).first() is None
- comment_id = comment["object"]["id"]
+ assert TextComment.query \
+ .filter_by(public_id=comment["object"]["id"]) \
+ .first() is None
+
+ assert "id" in comment["object"]
# Check we've got a delete activity back
assert "id" in delete
@@ -662,6 +716,8 @@ class TestAPI(object):
comment = self._activity_to_feed(test_app, activity)[1]
# Verify the comment reflects the changes
- model = TextComment.query.filter_by(public_id=comment["object"]["id"]).first()
+ model = TextComment.query \
+ .filter_by(public_id=comment["object"]["id"]) \
+ .first()
assert model.content == activity["object"]["content"]
diff --git a/mediagoblin/tests/test_auth.py b/mediagoblin/tests/test_auth.py
index cb971fdb..9cf5ccb0 100644
--- a/mediagoblin/tests/test_auth.py
+++ b/mediagoblin/tests/test_auth.py
@@ -1,4 +1,3 @@
-
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
@@ -102,7 +101,7 @@ def test_register_views(test_app):
'password': 'iamsohappy',
'email': 'easter@egg.com'})
- ## At this point there should on user in the database
+ ## At this point there should be one user in the database
assert User.query.count() == 1
# Successful register
@@ -373,6 +372,53 @@ def test_authentication_views(test_app):
assert not form.username.data == u'ANDREW'
assert form.username.data == u'andrew'
+ # Successful login with short user
+ # --------------------------------
+ short_user = fixture_add_user(username=u'me', password=u'sho')
+ template.clear_test_template_context()
+ response = test_app.post(
+ '/auth/login/', {
+ 'username': u'me',
+ 'password': 'sho'})
+
+ # User should be redirected
+ response.follow()
+
+ assert urlparse.urlsplit(response.location)[2] == '/'
+ assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
+
+ # Make sure user is in the session
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
+ session = context['request'].session
+ assert session['user_id'] == six.text_type(short_user.id)
+
+ # Must logout
+ template.clear_test_template_context()
+ response = test_app.get('/auth/logout/')
+
+ # Successful login with long user
+ # ----------------
+ long_user = fixture_add_user(
+ username=u'realllylonguser@reallylongdomain.com.co', password=u'sho')
+ template.clear_test_template_context()
+ response = test_app.post(
+ '/auth/login/', {
+ 'username': u'realllylonguser@reallylongdomain.com.co',
+ 'password': 'sho'})
+
+ # User should be redirected
+ response.follow()
+ assert urlparse.urlsplit(response.location)[2] == '/'
+ assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
+
+ # Make sure user is in the session
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
+ session = context['request'].session
+ assert session['user_id'] == six.text_type(long_user.id)
+
+ template.clear_test_template_context()
+ response = test_app.get('/auth/logout/')
+
@pytest.fixture()
def authentication_disabled_app(request):
return get_app(
diff --git a/mediagoblin/tests/test_celery_setup.py b/mediagoblin/tests/test_celery_setup.py
index df0d04b0..0749c7f4 100644
--- a/mediagoblin/tests/test_celery_setup.py
+++ b/mediagoblin/tests/test_celery_setup.py
@@ -55,7 +55,4 @@ def test_setup_celery_from_config():
'sqlite:///' +
pkg_resources.resource_filename('mediagoblin.tests', 'celery.db'))
- assert fake_celery_module.BROKER_TRANSPORT == 'sqlalchemy'
- assert fake_celery_module.BROKER_URL == (
- 'sqlite:///' +
- pkg_resources.resource_filename('mediagoblin.tests', 'kombu.db'))
+ assert fake_celery_module.BROKER_URL == 'amqp://'
diff --git a/mediagoblin/tests/test_config.py b/mediagoblin/tests/test_config.py
index b13adae6..c3527418 100644
--- a/mediagoblin/tests/test_config.py
+++ b/mediagoblin/tests/test_config.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+#
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
@@ -47,7 +49,7 @@ def test_read_mediagoblin_config():
assert this_conf['carrotapp']['num_carrots'] == 88
assert this_conf['carrotapp']['encouragement_phrase'] == \
"I'd love it if you eat your carrots!"
- assert this_conf['carrotapp']['blah_blah'] == "blah!"
+ assert this_conf['carrotapp']['blah_blah'] == u"blæh!"
assert this_conf['celery']['EAT_CELERY_WITH_CARROTS'] == False
# A bad file
diff --git a/mediagoblin/tests/test_exif.py b/mediagoblin/tests/test_exif.py
index d0495a7a..ad771cca 100644
--- a/mediagoblin/tests/test_exif.py
+++ b/mediagoblin/tests/test_exif.py
@@ -24,7 +24,7 @@ from collections import OrderedDict
from mediagoblin.tools.exif import exif_fix_image_orientation, \
extract_exif, clean_exif, get_gps_data, get_useful
-from .resources import GOOD_JPG, EMPTY_JPG, BAD_JPG, GPS_JPG
+from .resources import GOOD_JPG, EMPTY_JPG, BAD_JPG, GPS_JPG, BAD_GPS_JPG
def assert_in(a, b):
@@ -437,3 +437,18 @@ def test_exif_gps_data():
'direction': 25.674046740467404,
'altitude': 37.64365671641791,
'longitude': 18.016166666666667}
+
+
+def test_exif_bad_gps_data():
+ '''
+ Test extraction of GPS data from an image with bad GPS data
+ '''
+ result = extract_exif(BAD_GPS_JPG)
+ gps = get_gps_data(result)
+ print(gps)
+
+ assert gps == {
+ 'latitude': 0.0,
+ 'direction': 0.0,
+ 'altitude': 0.0,
+ 'longitude': 0.0}
diff --git a/mediagoblin/tests/test_exif/bad-gps.jpg b/mediagoblin/tests/test_exif/bad-gps.jpg
new file mode 100644
index 00000000..bd6c7bf2
--- /dev/null
+++ b/mediagoblin/tests/test_exif/bad-gps.jpg
Binary files differ
diff --git a/mediagoblin/tests/test_tools.py b/mediagoblin/tests/test_tools.py
index 6d3dd475..5f916400 100644
--- a/mediagoblin/tests/test_tools.py
+++ b/mediagoblin/tests/test_tools.py
@@ -16,10 +16,16 @@
from __future__ import absolute_import, unicode_literals
+try:
+ import mock
+except ImportError:
+ import unittest.mock as mock
+
from werkzeug.wrappers import Request
from werkzeug.test import EnvironBuilder
from mediagoblin.tools.request import decode_request
+from mediagoblin.tools.pagination import Pagination
class TestDecodeRequest(object):
"""Test the decode_request function."""
@@ -59,3 +65,54 @@ class TestDecodeRequest(object):
request.form = {'foo': 'bar'}
data = decode_request(request)
assert data['foo'] == 'bar'
+
+
+class TestPagination(object):
+ def _create_paginator(self, num_items, page, per_page):
+ """Create a Paginator with a mock database cursor."""
+ mock_cursor = mock.MagicMock()
+ mock_cursor.count.return_value = num_items
+ return Pagination(page, mock_cursor, per_page)
+
+ def test_creates_valid_page_url_from_explicit_base_url(self):
+ """Check that test_page_url_explicit runs.
+
+ This is a regression test for a Python 2/3 compatibility fix.
+
+ """
+ paginator = self._create_paginator(num_items=1, page=1, per_page=30)
+ url = paginator.get_page_url_explicit('http://example.com', [], 1)
+ assert url == 'http://example.com?page=1'
+
+ def test_iter_pages_handles_single_page(self):
+ """Check that iter_pages produces the expected result for single page.
+
+ This is a regression test for a Python 2/3 compatibility fix.
+
+ """
+ paginator = self._create_paginator(num_items=1, page=1, per_page=30)
+ assert list(paginator.iter_pages()) == [1]
+
+ def test_zero_items(self):
+ """Check that no items produces no pages."""
+ paginator = self._create_paginator(num_items=0, page=1, per_page=30)
+ assert paginator.total_count == 0
+ assert paginator.pages == 0
+
+ def test_single_item(self):
+ """Check that one item produces one page."""
+ paginator = self._create_paginator(num_items=1, page=1, per_page=30)
+ assert paginator.total_count == 1
+ assert paginator.pages == 1
+
+ def test_full_page(self):
+ """Check that a full page of items produces one page."""
+ paginator = self._create_paginator(num_items=30, page=1, per_page=30)
+ assert paginator.total_count == 30
+ assert paginator.pages == 1
+
+ def test_multiple_pages(self):
+ """Check that more than a full page produces two pages."""
+ paginator = self._create_paginator(num_items=31, page=1, per_page=30)
+ assert paginator.total_count == 31
+ assert paginator.pages == 2
diff --git a/mediagoblin/tests/test_util.py b/mediagoblin/tests/test_util.py
index 8193233f..02976405 100644
--- a/mediagoblin/tests/test_util.py
+++ b/mediagoblin/tests/test_util.py
@@ -19,6 +19,7 @@ try:
except ImportError:
import unittest.mock as mock
import email
+import socket
import pytest
import smtplib
import pkg_resources
@@ -26,6 +27,7 @@ import pkg_resources
import six
from mediagoblin.tests.tools import get_app
+from mediagoblin import mg_globals
from mediagoblin.tools import common, url, translate, mail, text, testing
testing._activate_testing()
@@ -181,3 +183,30 @@ def test_html_cleaner():
'<p><a href="javascript:nasty_surprise">innocent link!</a></p>')
assert result == (
'<p><a href="">innocent link!</a></p>')
+
+
+class TestMail(object):
+ """ Test mediagoblin's mail tool """
+ def test_no_mail_server(self):
+ """ Tests that no smtp server is available """
+ with pytest.raises(mail.NoSMTPServerError), mock.patch("smtplib.SMTP") as smtp_mock:
+ smtp_mock.side_effect = socket.error
+ mg_globals.app_config = {
+ "email_debug_mode": False,
+ "email_smtp_use_ssl": False,
+ "email_smtp_host": "127.0.0.1",
+ "email_smtp_port": 0}
+ common.TESTS_ENABLED = False
+ mail.send_email("", "", "", "")
+
+ def test_no_smtp_host(self):
+ """ Empty email_smtp_host """
+ with pytest.raises(mail.NoSMTPServerError), mock.patch("smtplib.SMTP") as smtp_mock:
+ smtp_mock.return_value.connect.side_effect = socket.error
+ mg_globals.app_config = {
+ "email_debug_mode": False,
+ "email_smtp_use_ssl": False,
+ "email_smtp_host": "",
+ "email_smtp_port": 0}
+ common.TESTS_ENABLED = False
+ mail.send_email("", "", "", "")
diff --git a/mediagoblin/tests/tools.py b/mediagoblin/tests/tools.py
index 39b9ac50..82def02c 100644
--- a/mediagoblin/tests/tools.py
+++ b/mediagoblin/tests/tools.py
@@ -153,28 +153,6 @@ def install_fixtures_simple(db, fixtures):
collection.insert(fixture)
-def assert_db_meets_expected(db, expected):
- """
- Assert a database contains the things we expect it to.
-
- Objects are found via 'id', so you should make sure your document
- has an id.
-
- Args:
- - db: pymongo or mongokit database connection
- - expected: the data we expect. Formatted like:
- {'collection_name': [
- {'id': 'foo',
- 'some_field': 'some_value'},]}
- """
- for collection_name, collection_data in six.iteritems(expected):
- collection = db[collection_name]
- for expected_document in collection_data:
- document = collection.query.filter_by(id=expected_document['id']).first()
- assert document is not None # make sure it exists
- assert document == expected_document # make sure it matches
-
-
def fixture_add_user(username=u'chris', password=u'toast',
privileges=[], wants_comment_notification=True):
# Reuse existing user or create a new one
diff --git a/mediagoblin/themes/airy/assets/css/airy.css b/mediagoblin/themes/airy/assets/css/airy.css
index 7539997e..047e02dc 100644
--- a/mediagoblin/themes/airy/assets/css/airy.css
+++ b/mediagoblin/themes/airy/assets/css/airy.css
@@ -52,6 +52,10 @@ footer {
border-top: 1px solid #E4E4E4;
}
+table.admin_panel th {
+ color: #4a4a4a;
+}
+
.button_action, .button_action_highlight, .button_form {
color: #4a4a4a;
background-color: #fff;
diff --git a/mediagoblin/tools/exif.py b/mediagoblin/tools/exif.py
index fafd987d..2215fb0c 100644
--- a/mediagoblin/tools/exif.py
+++ b/mediagoblin/tools/exif.py
@@ -19,6 +19,11 @@ import six
from exifread import process_file
from exifread.utils import Ratio
+try:
+ from PIL import Image
+except ImportError:
+ import Image
+
from mediagoblin.processing import BadMediaFail
from mediagoblin.tools.translate import pass_to_ugettext as _
@@ -61,12 +66,12 @@ def exif_fix_image_orientation(im, exif_tags):
# Rotate image
if 'Image Orientation' in exif_tags:
rotation_map = {
- 3: 180,
- 6: 270,
- 8: 90}
+ 3: Image.ROTATE_180,
+ 6: Image.ROTATE_270,
+ 8: Image.ROTATE_90}
orientation = exif_tags['Image Orientation'].values[0]
if orientation in rotation_map:
- im = im.rotate(
+ im = im.transpose(
rotation_map[orientation])
return im
@@ -175,18 +180,14 @@ def get_gps_data(tags):
pass
try:
- gps_data['direction'] = (
- lambda d:
- float(d.num) / float(d.den)
- )(tags['GPS GPSImgDirection'].values[0])
+ direction = tags['GPS GPSImgDirection'].values[0]
+ gps_data['direction'] = safe_gps_ratio_divide(direction)
except KeyError:
pass
try:
- gps_data['altitude'] = (
- lambda a:
- float(a.num) / float(a.den)
- )(tags['GPS GPSAltitude'].values[0])
+ altitude = tags['GPS GPSAltitude'].values[0]
+ gps_data['altitude'] = safe_gps_ratio_divide(altitude)
except KeyError:
pass
diff --git a/mediagoblin/tools/licenses.py b/mediagoblin/tools/licenses.py
index a964980e..2aff7f20 100644
--- a/mediagoblin/tools/licenses.py
+++ b/mediagoblin/tools/licenses.py
@@ -20,28 +20,45 @@ License = namedtuple("License", ["abbreviation", "name", "uri"])
SORTED_LICENSES = [
License("All rights reserved", "No license specified", ""),
+ License("CC BY 4.0", "Creative Commons Attribution 4.0 International",
+ "https://creativecommons.org/licenses/by/4.0/"),
+ License("CC BY-SA 4.0",
+ "Creative Commons Attribution-ShareAlike 4.0 International",
+ "https://creativecommons.org/licenses/by-sa/4.0/"),
+ License("CC BY-ND 4.0",
+ "Creative Commons Attribution-NoDerivs 4.0 International",
+ "https://creativecommons.org/licenses/by-nd/4.0/"),
+ License("CC BY-NC 4.0",
+ "Creative Commons Attribution-NonCommercial 4.0 International",
+ "https://creativecommons.org/licenses/by-nc/4.0/"),
+ License("CC BY-NC-SA 4.0",
+ "Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International",
+ "https://creativecommons.org/licenses/by-nc-sa/4.0/"),
+ License("CC BY-NC-ND 4.0",
+ "Creative Commons Attribution-NonCommercial-NoDerivs 4.0 International",
+ "https://creativecommons.org/licenses/by-nc-nd/4.0/"),
License("CC BY 3.0", "Creative Commons Attribution Unported 3.0",
- "http://creativecommons.org/licenses/by/3.0/"),
+ "https://creativecommons.org/licenses/by/3.0/"),
License("CC BY-SA 3.0",
- "Creative Commons Attribution-ShareAlike Unported 3.0",
- "http://creativecommons.org/licenses/by-sa/3.0/"),
+ "Creative Commons Attribution-ShareAlike 3.0 Unported",
+ "https://creativecommons.org/licenses/by-sa/3.0/"),
License("CC BY-ND 3.0",
"Creative Commons Attribution-NoDerivs 3.0 Unported",
- "http://creativecommons.org/licenses/by-nd/3.0/"),
+ "https://creativecommons.org/licenses/by-nd/3.0/"),
License("CC BY-NC 3.0",
- "Creative Commons Attribution-NonCommercial Unported 3.0",
- "http://creativecommons.org/licenses/by-nc/3.0/"),
+ "Creative Commons Attribution-NonCommercial 3.0 Unported",
+ "https://creativecommons.org/licenses/by-nc/3.0/"),
License("CC BY-NC-SA 3.0",
"Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported",
- "http://creativecommons.org/licenses/by-nc-sa/3.0/"),
+ "https://creativecommons.org/licenses/by-nc-sa/3.0/"),
License("CC BY-NC-ND 3.0",
"Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported",
- "http://creativecommons.org/licenses/by-nc-nd/3.0/"),
+ "https://creativecommons.org/licenses/by-nc-nd/3.0/"),
License("CC0 1.0",
"Creative Commons CC0 1.0 Universal",
- "http://creativecommons.org/publicdomain/zero/1.0/"),
+ "https://creativecommons.org/publicdomain/zero/1.0/"),
License("Public Domain","Public Domain",
- "http://creativecommons.org/publicdomain/mark/1.0/"),
+ "https://creativecommons.org/publicdomain/mark/1.0/"),
]
# dict {uri: License,...} to enable fast license lookup by uri. Ideally,
diff --git a/mediagoblin/tools/mail.py b/mediagoblin/tools/mail.py
index c11e392b..3dc180d8 100644
--- a/mediagoblin/tools/mail.py
+++ b/mediagoblin/tools/mail.py
@@ -16,6 +16,8 @@
from __future__ import print_function, unicode_literals
+import socket
+import logging
import six
import smtplib
import sys
@@ -54,6 +56,14 @@ EMAIL_TEST_INBOX = []
EMAIL_TEST_MBOX_INBOX = []
+class MailError(Exception):
+ """ General exception for mail errors """
+
+
+class NoSMTPServerError(MailError):
+ pass
+
+
class FakeMhost(object):
"""
Just a fake mail host so we can capture and test messages
@@ -101,13 +111,27 @@ def send_email(from_addr, to_addrs, subject, message_body):
else:
smtp_init = smtplib.SMTP
- mhost = smtp_init(
- mg_globals.app_config['email_smtp_host'],
- mg_globals.app_config['email_smtp_port'])
+ try:
+ mhost = smtp_init(
+ mg_globals.app_config['email_smtp_host'],
+ mg_globals.app_config['email_smtp_port'])
+ except socket.error as original_error:
+ error_message = "Couldn't contact mail server on <{}>:<{}>".format(
+ mg_globals.app_config['email_smtp_host'],
+ mg_globals.app_config['email_smtp_port'])
+ logging.debug(original_error)
+ raise NoSMTPServerError(error_message)
# SMTP.__init__ Issues SMTP.connect implicitly if host
if not mg_globals.app_config['email_smtp_host']: # e.g. host = ''
- mhost.connect() # We SMTP.connect explicitly
+ try:
+ mhost.connect() # We SMTP.connect explicitly
+ except socket.error as original_error:
+ error_message = "Couldn't contact mail server on <{}>:<{}>".format(
+ mg_globals.app_config['email_smtp_host'],
+ mg_globals.app_config['email_smtp_port'])
+ logging.debug(original_error)
+ raise NoSMTPServerError(error_message)
try:
mhost.starttls()
diff --git a/mediagoblin/tools/pagination.py b/mediagoblin/tools/pagination.py
index a525caf7..db5f69fb 100644
--- a/mediagoblin/tools/pagination.py
+++ b/mediagoblin/tools/pagination.py
@@ -14,13 +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/>.
-import urllib
import copy
from math import ceil, floor
from itertools import count
from werkzeug.datastructures import MultiDict
-from six.moves import zip
+from six.moves import range, urllib, zip
PAGINATION_DEFAULT_PER_PAGE = 30
@@ -86,7 +85,7 @@ class Pagination(object):
def iter_pages(self, left_edge=2, left_current=2,
right_current=5, right_edge=2):
last = 0
- for num in xrange(1, self.pages + 1):
+ for num in range(1, self.pages + 1):
if num <= left_edge or \
(num > self.page - left_current - 1 and \
num < self.page + right_current) or \
@@ -107,7 +106,7 @@ class Pagination(object):
new_get_params['page'] = page_no
return "%s?%s" % (
- base_url, urllib.urlencode(new_get_params))
+ base_url, urllib.parse.urlencode(new_get_params))
def get_page_url(self, request, page_no):
"""
diff --git a/mediagoblin/tools/theme.py b/mediagoblin/tools/theme.py
index 97b041a6..79fd91e1 100644
--- a/mediagoblin/tools/theme.py
+++ b/mediagoblin/tools/theme.py
@@ -68,7 +68,7 @@ def register_themes(app_config, builtin_dir=BUILTIN_THEME_DIR):
themedata = themedata_for_theme_dir(themedir, abs_themedir)
registry[themedir] = themedata
-
+
# Built-in themes
if os.path.exists(builtin_dir):
_install_themes_in_dir(builtin_dir)
@@ -79,11 +79,9 @@ def register_themes(app_config, builtin_dir=BUILTIN_THEME_DIR):
_install_themes_in_dir(theme_install_dir)
current_theme_name = app_config.get('theme')
- if current_theme_name \
- and registry.has_key(current_theme_name):
+ try:
current_theme = registry[current_theme_name]
- else:
+ except KeyError:
current_theme = None
return registry, current_theme
-
diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py
index 28d3ba79..b4737ea8 100644
--- a/mediagoblin/user_pages/views.py
+++ b/mediagoblin/user_pages/views.py
@@ -24,6 +24,7 @@ from mediagoblin import messages, mg_globals
from mediagoblin.db.models import (MediaEntry, MediaTag, Collection, Comment,
CollectionItem, LocalUser, Activity, \
GenericModelReference)
+from mediagoblin.plugins.api.tools import get_media_file_paths
from mediagoblin.tools.response import render_to_response, render_404, \
redirect, redirect_obj
from mediagoblin.tools.text import cleaned_markdown_conversion
@@ -179,6 +180,10 @@ def media_post_comment(request, media):
if not request.method == 'POST':
raise MethodNotAllowed()
+ # If media is not processed, return NotFound.
+ if not media.state == u'processed':
+ return render_404(request)
+
comment = request.db.TextComment()
comment.actor = request.user.id
comment.content = six.text_type(request.form['comment_content'])
@@ -231,6 +236,10 @@ def media_preview_comment(request):
def media_collect(request, media):
"""Add media to collection submission"""
+ # If media is not processed, return NotFound.
+ if not media.state == u'processed':
+ return render_404(request)
+
form = user_forms.MediaCollectForm(request.form)
# A user's own collections:
form.collection.query = Collection.query.filter_by(
@@ -288,12 +297,6 @@ def media_collect(request, media):
collection = None
# Make sure the user actually selected a collection
- item = CollectionItem.query.filter_by(collection=collection.id)
- item = item.join(CollectionItem.object_helper).filter_by(
- model_type=media.__tablename__,
- obj_pk=media.id
- ).first()
-
if not collection:
messages.add_message(
request,
@@ -303,8 +306,14 @@ def media_collect(request, media):
user=media.get_actor.username,
media_id=media.id)
+ item = CollectionItem.query.filter_by(collection=collection.id)
+ item = item.join(CollectionItem.object_helper).filter_by(
+ model_type=media.__tablename__,
+ obj_pk=media.id
+ ).first()
+
# Check whether media already exists in collection
- elif item is not None:
+ if item is not None:
messages.add_message(
request,
messages.ERROR,
@@ -538,23 +547,21 @@ def atom_feed(request):
username = request.matchdict['user']).first()
if not user or not user.has_privilege(u'active'):
return render_404(request)
+ feed_title = "MediaGoblin Feed for user '%s'" % request.matchdict['user']
+ link = request.urlgen('mediagoblin.user_pages.user_home',
+ qualified=True, user=request.matchdict['user'])
+ cursor = MediaEntry.query.filter_by(actor=user.id, state=u'processed')
+ cursor = cursor.order_by(MediaEntry.created.desc())
+ cursor = cursor.limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS)
- cursor = MediaEntry.query.filter_by(
- actor = user.id,
- state = u'processed').\
- order_by(MediaEntry.created.desc()).\
- limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS)
"""
ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI)
"""
atomlinks = [{
- 'href': request.urlgen(
- 'mediagoblin.user_pages.user_home',
- qualified=True, user=request.matchdict['user']),
- 'rel': 'alternate',
- 'type': 'text/html'
- }]
+ 'href': link,
+ 'rel': 'alternate',
+ 'type': 'text/html'}]
if mg_globals.app_config["push_urls"]:
for push_url in mg_globals.app_config["push_urls"]:
@@ -563,25 +570,34 @@ def atom_feed(request):
'href': push_url})
feed = AtomFeed(
- "MediaGoblin: Feed for user '%s'" % request.matchdict['user'],
- feed_url=request.url,
- id='tag:{host},{year}:gallery.user-{user}'.format(
- host=request.host,
- year=datetime.datetime.today().strftime('%Y'),
- user=request.matchdict['user']),
- links=atomlinks)
+ feed_title,
+ feed_url=request.url,
+ id='tag:{host},{year}:gallery.user-{user}'.format(
+ host=request.host,
+ year=datetime.datetime.today().strftime('%Y'),
+ user=request.matchdict['user']),
+ links=atomlinks)
for entry in cursor:
+ # Include a thumbnail image in content.
+ file_urls = get_media_file_paths(entry.media_files, request.urlgen)
+ if 'thumb' in file_urls:
+ content = u'<img src="{thumb}" alt='' /> {desc}'.format(
+ thumb=file_urls['thumb'], desc=entry.description_html)
+ else:
+ content = entry.description_html
+
feed.add(
entry.get('title'),
- entry.description_html,
+ content,
id=entry.url_for_self(request.urlgen, qualified=True),
content_type='html',
author={
'name': entry.get_actor.username,
'uri': request.urlgen(
'mediagoblin.user_pages.user_home',
- qualified=True, user=entry.get_actor.username)},
+ qualified=True,
+ user=entry.get_actor.username)},
updated=entry.get('created'),
links=[{
'href': entry.url_for_self(