aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore9
-rw-r--r--mediagoblin.ini4
-rw-r--r--mediagoblin/app.py15
-rw-r--r--mediagoblin/config_spec.ini81
-rw-r--r--mediagoblin/db/mixin.py23
-rw-r--r--mediagoblin/db/mongo/migrations.py38
-rw-r--r--mediagoblin/db/mongo/models.py32
-rw-r--r--mediagoblin/db/mongo/util.py11
-rw-r--r--mediagoblin/db/sql/base.py10
-rw-r--r--mediagoblin/db/sql/convert.py37
-rw-r--r--mediagoblin/db/sql/extratypes.py28
-rw-r--r--mediagoblin/db/sql/migrations.py17
-rw-r--r--mediagoblin/db/sql/models.py82
-rw-r--r--mediagoblin/db/sql/open.py1
-rw-r--r--mediagoblin/db/sql/util.py284
-rw-r--r--mediagoblin/db/util.py4
-rw-r--r--mediagoblin/edit/views.py9
-rw-r--r--mediagoblin/gmg_commands/__init__.py8
-rw-r--r--mediagoblin/gmg_commands/dbupdate.py89
-rw-r--r--mediagoblin/gmg_commands/mongosql.py25
-rw-r--r--mediagoblin/i18n/en/LC_MESSAGES/mediagoblin.po46
-rw-r--r--mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.mobin13840 -> 13828 bytes
-rw-r--r--mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.po102
-rw-r--r--mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.mobin18051 -> 18027 bytes
-rw-r--r--mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.po108
-rw-r--r--mediagoblin/init/celery/__init__.py24
-rw-r--r--mediagoblin/listings/views.py2
-rw-r--r--mediagoblin/media_types/ascii/migrations.py17
-rw-r--r--mediagoblin/media_types/ascii/models.py34
-rw-r--r--mediagoblin/media_types/image/migrations.py17
-rw-r--r--mediagoblin/media_types/image/models.py19
-rw-r--r--mediagoblin/media_types/video/migrations.py17
-rw-r--r--mediagoblin/media_types/video/models.py35
-rw-r--r--mediagoblin/media_types/video/processing.py6
-rw-r--r--mediagoblin/processing.py19
-rw-r--r--mediagoblin/static/images/goblin.icobin318 -> 318 bytes
-rw-r--r--mediagoblin/static/images/goblin.pngbin413 -> 413 bytes
-rw-r--r--mediagoblin/submit/views.py24
-rw-r--r--mediagoblin/templates/mediagoblin/media_displays/video.html4
-rw-r--r--mediagoblin/tests/fake_carrot_conf_bad.ini2
-rw-r--r--mediagoblin/tests/fake_carrot_conf_good.ini2
-rw-r--r--mediagoblin/tests/fake_celery_conf.ini14
-rw-r--r--mediagoblin/tests/fake_celery_conf_mgdb.ini14
-rw-r--r--mediagoblin/tests/fake_config_spec.ini2
-rw-r--r--mediagoblin/tests/test_celery_setup.py43
-rw-r--r--mediagoblin/tests/test_config.py8
-rw-r--r--mediagoblin/tests/test_mgoblin_app.ini2
-rw-r--r--mediagoblin/tests/test_paste.ini2
-rw-r--r--mediagoblin/tests/test_sql_migrations.py884
-rw-r--r--mediagoblin/tests/test_submission.py6
-rw-r--r--mediagoblin/tools/files.py2
-rw-r--r--mediagoblin/user_pages/views.py12
-rw-r--r--setup.py3
53 files changed, 1886 insertions, 391 deletions
diff --git a/.gitignore b/.gitignore
index b46ec38a..95f36a7b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,13 +15,20 @@
/user_dev/
/paste_local.ini
/mediagoblin_local.ini
+/mediagoblin.db
+/celery.db
+/kombu.db
/server-log.txt
+/mediagoblin/db/sql_switch.py
# Tests
/mediagoblin/tests/user_dev/
-.installed.cfg
+# File extensions
*.pyc
*.pyo
*~
*.swp
+
+# The legacy of buildout
+.installed.cfg \ No newline at end of file
diff --git a/mediagoblin.ini b/mediagoblin.ini
index dbde6e51..223f0f4a 100644
--- a/mediagoblin.ini
+++ b/mediagoblin.ini
@@ -5,6 +5,10 @@
direct_remote_path = /mgoblin_static/
email_sender_address = "notice@mediagoblin.example.org"
+## Uncomment and change to your DB's appropiate setting.
+## Default is a local sqlite db "mediagoblin.db".
+# sql_engine = postgresql:///gmg
+
# set to false to enable sending notices
email_debug_mode = true
diff --git a/mediagoblin/app.py b/mediagoblin/app.py
index 06627675..0a57c091 100644
--- a/mediagoblin/app.py
+++ b/mediagoblin/app.py
@@ -16,11 +16,12 @@
import os
import urllib
+import logging
import routes
from webob import Request, exc
-from mediagoblin import routing, meddleware
+from mediagoblin import routing, meddleware, __version__
from mediagoblin.tools import common, translate, template
from mediagoblin.tools.response import render_404
from mediagoblin.tools import request as mg_request
@@ -31,6 +32,9 @@ from mediagoblin.init import (get_jinja_loader, get_staticdirector,
setup_storage, setup_beaker_cache)
+_log = logging.getLogger(__name__)
+
+
class MediaGoblinApp(object):
"""
WSGI application of MediaGoblin
@@ -47,6 +51,7 @@ class MediaGoblinApp(object):
(Note: setting 'celery_setup_elsewhere' also disables
setting up celery.)
"""
+ _log.info("GNU MediaGoblin %s main server starting", __version__)
##############
# Setup config
##############
@@ -179,6 +184,14 @@ class MediaGoblinApp(object):
for m in self.meddleware[::-1]:
m.process_response(request, response)
+ # Reset the sql session, so that the next request
+ # gets a fresh session
+ try:
+ self.db.reset_after_request()
+ except TypeError:
+ # We're still on mongo
+ pass
+
return response(environ, start_response)
diff --git a/mediagoblin/config_spec.ini b/mediagoblin/config_spec.ini
index 2d410899..10828536 100644
--- a/mediagoblin/config_spec.ini
+++ b/mediagoblin/config_spec.ini
@@ -9,6 +9,7 @@ media_types = string_list(default=list("mediagoblin.media_types.image"))
db_host = string()
db_name = string(default="mediagoblin")
db_port = integer()
+sql_engine = string(default="sqlite:///%(here)s/mediagoblin.db")
# Where temporary files used in processing and etc are kept
workbench_path = string(default="%(here)s/user_dev/media/workbench")
@@ -78,46 +79,54 @@ lock_dir = string(default="%(here)s/user_dev/beaker/cache/lock")
[celery]
+# default result stuff
+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_HOST = string(default="sqlite:///%(here)s/kombu.db")
+
# known booleans
-celery_result_persistent = boolean()
-celery_create_missing_queues = boolean()
-broker_use_ssl = boolean()
-broker_connection_retry = boolean()
-celery_always_eager = boolean()
-celery_eager_propagates_exceptions = boolean()
-celery_ignore_result = boolean()
-celery_track_started = boolean()
-celery_disable_rate_limits = boolean()
-celery_acks_late = boolean()
-celery_store_errors_even_if_ignored = boolean()
-celery_send_task_error_emails = boolean()
-celery_send_events = boolean()
-celery_send_task_sent_event = boolean()
-celeryd_log_color = boolean()
-celery_redirect_stdouts = boolean()
+CELERY_RESULT_PERSISTENT = boolean()
+CELERY_CREATE_MISSING_QUEUES = boolean()
+BROKER_USE_SSL = boolean()
+BROKER_CONNECTION_RETRY = boolean()
+CELERY_ALWAYS_EAGER = boolean()
+CELERY_EAGER_PROPAGATES_EXCEPTIONS = boolean()
+CELERY_IGNORE_RESULT = boolean()
+CELERY_TRACK_STARTED = boolean()
+CELERY_DISABLE_RATE_LIMITS = boolean()
+CELERY_ACKS_LATE = boolean()
+CELERY_STORE_ERRORS_EVEN_IF_IGNORED = boolean()
+CELERY_SEND_TASK_ERROR_EMAILS = boolean()
+CELERY_SEND_EVENTS = boolean()
+CELERY_SEND_TASK_SENT_EVENT = boolean()
+CELERYD_LOG_COLOR = boolean()
+CELERY_REDIRECT_STDOUTS = boolean()
# known ints
-celeryd_concurrency = integer()
-celeryd_prefetch_multiplier = integer()
-celery_amqp_task_result_expires = integer()
-celery_amqp_task_result_connection_max = integer()
-redis_port = integer()
-redis_db = integer()
-broker_port = integer()
-broker_connection_timeout = integer()
-celery_broker_connection_max_retries = integer()
-celery_task_result_expires = integer()
-celery_max_cached_results = integer()
-celery_default_rate_limit = integer()
-celeryd_max_tasks_per_child = integer()
-celeryd_task_time_limit = integer()
-celeryd_task_soft_time_limit = integer()
-mail_port = integer()
-celerybeat_max_loop_interval = integer()
+CELERYD_CONCURRENCY = integer()
+CELERYD_PREFETCH_MULTIPLIER = integer()
+CELERY_AMQP_TASK_RESULT_EXPIRES = integer()
+CELERY_AMQP_TASK_RESULT_CONNECTION_MAX = integer()
+REDIS_PORT = integer()
+REDIS_DB = integer()
+BROKER_PORT = integer()
+BROKER_CONNECTION_TIMEOUT = integer()
+CELERY_BROKER_CONNECTION_MAX_RETRIES = integer()
+CELERY_TASK_RESULT_EXPIRES = integer()
+CELERY_MAX_CACHED_RESULTS = integer()
+CELERY_DEFAULT_RATE_LIMIT = integer()
+CELERYD_MAX_TASKS_PER_CHILD = integer()
+CELERYD_TASK_TIME_LIMIT = integer()
+CELERYD_TASK_SOFT_TIME_LIMIT = integer()
+MAIL_PORT = integer()
+CELERYBEAT_MAX_LOOP_INTERVAL = integer()
# known floats
-celeryd_eta_scheduler_precision = float()
+CELERYD_ETA_SCHEDULER_PRECISION = float()
# known lists
-celery_routes = string_list()
-celery_imports = string_list()
+CELERY_ROUTES = string_list()
+CELERY_IMPORTS = string_list()
diff --git a/mediagoblin/db/mixin.py b/mediagoblin/db/mixin.py
index beaff9b0..758f7e72 100644
--- a/mediagoblin/db/mixin.py
+++ b/mediagoblin/db/mixin.py
@@ -29,6 +29,7 @@ real objects.
from mediagoblin.auth import lib as auth_lib
from mediagoblin.tools import common, licenses
+from mediagoblin.tools.text import cleaned_markdown_conversion
class UserMixin(object):
@@ -39,8 +40,20 @@ class UserMixin(object):
return auth_lib.bcrypt_check_password(
password, self.pw_hash)
+ @property
+ def bio_html(self):
+ return cleaned_markdown_conversion(self.bio)
+
class MediaEntryMixin(object):
+ @property
+ def description_html(self):
+ """
+ Rendered version of the description, run through
+ Markdown and cleaned with our cleaning tool.
+ """
+ return cleaned_markdown_conversion(self.description)
+
def get_display_media(self, media_map,
fetch_order=common.DISPLAY_IMAGE_FETCHING_ORDER):
"""
@@ -91,3 +104,13 @@ class MediaEntryMixin(object):
def get_license_data(self):
"""Return license dict for requested license"""
return licenses.SUPPORTED_LICENSES[self.license or ""]
+
+
+class MediaCommentMixin(object):
+ @property
+ def content_html(self):
+ """
+ the actual html-rendered version of the comment displayed.
+ Run through Markdown and the HTML cleaner.
+ """
+ return cleaned_markdown_conversion(self.content)
diff --git a/mediagoblin/db/mongo/migrations.py b/mediagoblin/db/mongo/migrations.py
index 261e21a5..c5766b0d 100644
--- a/mediagoblin/db/mongo/migrations.py
+++ b/mediagoblin/db/mongo/migrations.py
@@ -29,6 +29,16 @@ def add_table_field(db, table_name, field_name, default_value):
multi=True)
+def drop_table_field(db, table_name, field_name):
+ """
+ Drop an old field from a table/collection
+ """
+ db[table_name].update(
+ {field_name: {'$exists': True}},
+ {'$unset': {field_name: 1}},
+ multi=True)
+
+
# Please see mediagoblin/tests/test_migrations.py for some examples of
# basic migrations.
@@ -115,3 +125,31 @@ def mediaentry_add_license(database):
Add the 'license' field for entries that don't have it.
"""
add_table_field(database, 'media_entries', 'license', None)
+
+
+@RegisterMigration(9)
+def remove_calculated_html(database):
+ """
+ Drop pre-rendered html again and calculate things
+ on the fly (and cache):
+ - User.bio_html
+ - MediaEntry.description_html
+ - MediaComment.content_html
+ """
+ drop_table_field(database, 'users', 'bio_html')
+ drop_table_field(database, 'media_entries', 'description_html')
+ drop_table_field(database, 'media_comments', 'content_html')
+
+@RegisterMigration(10)
+def convert_video_media_data(database):
+ """
+ Move media_data["video"] directly into media_data
+ """
+ collection = database['media_entries']
+ target = collection.find(
+ {'media_data.video': {'$exists': True}})
+
+ for document in target:
+ assert len(document['media_data']) == 1
+ document['media_data'] = document['media_data']['video']
+ collection.save(document)
diff --git a/mediagoblin/db/mongo/models.py b/mediagoblin/db/mongo/models.py
index 541086bc..c86adbb6 100644
--- a/mediagoblin/db/mongo/models.py
+++ b/mediagoblin/db/mongo/models.py
@@ -23,7 +23,18 @@ from mediagoblin.db.mongo import migrations
from mediagoblin.db.mongo.util import ASCENDING, DESCENDING, ObjectId
from mediagoblin.tools.pagination import Pagination
from mediagoblin.tools import url
-from mediagoblin.db.mixin import UserMixin, MediaEntryMixin
+from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin
+
+
+class MongoPK(object):
+ """An alias for the _id primary key"""
+ def __get__(self, instance, cls):
+ return instance['_id']
+ def __set__(self, instance, val):
+ instance['_id'] = val
+ def __delete__(self, instance):
+ del instance['_id']
+
###################
# Custom validators
@@ -59,7 +70,6 @@ class User(Document, UserMixin):
- is_admin: Whether or not this user is an administrator or not.
- url: this user's personal webpage/website, if appropriate.
- bio: biography of this user (plaintext, in markdown)
- - bio_html: biography of the user converted to proper HTML.
"""
__collection__ = 'users'
use_dot_notation = True
@@ -76,7 +86,6 @@ class User(Document, UserMixin):
'is_admin': bool,
'url': unicode,
'bio': unicode, # May contain markdown
- 'bio_html': unicode, # May contain plaintext, or HTML
'fp_verification_key': unicode, # forgotten password verification key
'fp_token_expire': datetime.datetime,
}
@@ -89,6 +98,8 @@ class User(Document, UserMixin):
'status': u'needs_email_verification',
'is_admin': False}
+ id = MongoPK()
+
class MediaEntry(Document, MediaEntryMixin):
"""
@@ -112,9 +123,6 @@ class MediaEntry(Document, MediaEntryMixin):
up with MarkDown for slight fanciness (links, boldness, italics,
paragraphs...)
- - description_html: Rendered version of the description, run through
- Markdown and cleaned with our cleaning tool.
-
- media_type: What type of media is this? Currently we only support
'image' ;)
@@ -179,7 +187,6 @@ class MediaEntry(Document, MediaEntryMixin):
'slug': unicode,
'created': datetime.datetime,
'description': unicode, # May contain markdown/up
- 'description_html': unicode, # May contain plaintext, or HTML
'media_type': unicode,
'media_data': dict, # extra data relevant to this media_type
'plugin_data': dict, # plugins can dump stuff here.
@@ -211,6 +218,11 @@ class MediaEntry(Document, MediaEntryMixin):
'created': datetime.datetime.utcnow,
'state': u'unprocessed'}
+ id = MongoPK()
+
+ def media_data_init(self, **kwargs):
+ self.media_data.update(kwargs)
+
def get_comments(self, ascending=False):
if ascending:
order = ASCENDING
@@ -257,7 +269,7 @@ class MediaEntry(Document, MediaEntryMixin):
return self.db.User.find_one({'_id': self.uploader})
-class MediaComment(Document):
+class MediaComment(Document, MediaCommentMixin):
"""
A comment on a MediaEntry.
@@ -266,8 +278,6 @@ class MediaComment(Document):
- author: user who posted this comment
- created: when the comment was created
- content: plaintext (but markdown'able) version of the comment's content.
- - content_html: the actual html-rendered version of the comment displayed.
- Run through Markdown and the HTML cleaner.
"""
__collection__ = 'media_comments'
@@ -278,7 +288,7 @@ class MediaComment(Document):
'author': ObjectId,
'created': datetime.datetime,
'content': unicode,
- 'content_html': unicode}
+ }
required_fields = [
'media_entry', 'author', 'created', 'content']
diff --git a/mediagoblin/db/mongo/util.py b/mediagoblin/db/mongo/util.py
index 4daf616a..89348d98 100644
--- a/mediagoblin/db/mongo/util.py
+++ b/mediagoblin/db/mongo/util.py
@@ -290,3 +290,14 @@ class MigrationManager(object):
self.set_current_migration(migration_number)
if post_callback:
post_callback(migration_number, migration_func)
+
+
+##########################
+# Random utility functions
+##########################
+
+
+def atomic_update(table, query_dict, update_values):
+ table.collection.update(
+ query_dict,
+ {"$set": update_values})
diff --git a/mediagoblin/db/sql/base.py b/mediagoblin/db/sql/base.py
index 6ed24a03..838080b0 100644
--- a/mediagoblin/db/sql/base.py
+++ b/mediagoblin/db/sql/base.py
@@ -67,6 +67,10 @@ class GMGTableBase(object):
def get(self, key):
return getattr(self, key)
+ def setdefault(self, key, defaultvalue):
+ # The key *has* to exist on sql.
+ return getattr(self, key)
+
def save(self, validate=True):
assert validate
sess = object_session(self)
@@ -75,6 +79,12 @@ class GMGTableBase(object):
sess.add(self)
sess.commit()
+ def delete(self):
+ sess = object_session(self)
+ assert sess is not None, "Not going to delete detached %r" % self
+ sess.delete(self)
+ sess.commit()
+
Base = declarative_base(cls=GMGTableBase)
diff --git a/mediagoblin/db/sql/convert.py b/mediagoblin/db/sql/convert.py
index f6575be9..250c559b 100644
--- a/mediagoblin/db/sql/convert.py
+++ b/mediagoblin/db/sql/convert.py
@@ -19,7 +19,8 @@ from mediagoblin.init import setup_global_and_app_config, setup_database
from mediagoblin.db.mongo.util import ObjectId
from mediagoblin.db.sql.models import (Base, User, MediaEntry, MediaComment,
- Tag, MediaTag, MediaFile)
+ Tag, MediaTag, MediaFile, MediaAttachmentFile)
+from mediagoblin.media_types.video.models import VideoData
from mediagoblin.db.sql.open import setup_connection_and_db_from_config as \
sql_connect
from mediagoblin.db.mongo.open import setup_connection_and_db_from_config as \
@@ -49,14 +50,14 @@ def copy_reference_attr(entry, new_entry, ref_attr):
def convert_users(mk_db):
session = Session()
- for entry in mk_db.User.find():
+ for entry in mk_db.User.find().sort('created'):
print entry.username
new_entry = User()
copy_attrs(entry, new_entry,
('username', 'email', 'created', 'pw_hash', 'email_verified',
'status', 'verification_key', 'is_admin', 'url',
- 'bio', 'bio_html',
+ 'bio',
'fp_verification_key', 'fp_token_expire',))
# new_entry.fp_verification_expire = entry.fp_token_expire
@@ -71,15 +72,15 @@ def convert_users(mk_db):
def convert_media_entries(mk_db):
session = Session()
- for entry in mk_db.MediaEntry.find():
+ for entry in mk_db.MediaEntry.find().sort('created'):
print repr(entry.title)
new_entry = MediaEntry()
copy_attrs(entry, new_entry,
('title', 'slug', 'created',
- 'description', 'description_html',
+ 'description',
'media_type', 'state', 'license',
- 'fail_error',
+ 'fail_error', 'fail_metadata',
'queued_task_id',))
copy_reference_attr(entry, new_entry, "uploader")
@@ -92,6 +93,15 @@ def convert_media_entries(mk_db):
new_file.media_entry = new_entry.id
Session.add(new_file)
+ for attachment in entry.attachment_files:
+ new_attach = MediaAttachmentFile(
+ name=attachment["name"],
+ filepath=attachment["filepath"],
+ created=attachment["created"]
+ )
+ new_attach.media_entry = new_entry.id
+ Session.add(new_attach)
+
session.commit()
session.close()
@@ -100,7 +110,7 @@ def convert_media_tags(mk_db):
session = Session()
session.autoflush = False
- for media in mk_db.MediaEntry.find():
+ for media in mk_db.MediaEntry.find().sort('created'):
print repr(media.title)
for otag in media.tags:
@@ -127,13 +137,13 @@ def convert_media_tags(mk_db):
def convert_media_comments(mk_db):
session = Session()
- for entry in mk_db.MediaComment.find():
+ for entry in mk_db.MediaComment.find().sort('created'):
print repr(entry.content)
new_entry = MediaComment()
copy_attrs(entry, new_entry,
('created',
- 'content', 'content_html',))
+ 'content',))
copy_reference_attr(entry, new_entry, "media_entry")
copy_reference_attr(entry, new_entry, "author")
@@ -145,11 +155,10 @@ def convert_media_comments(mk_db):
session.close()
-def main():
- global_config, app_config = setup_global_and_app_config("mediagoblin.ini")
-
- sql_conn, sql_db = sql_connect({'sql_engine': 'sqlite:///mediagoblin.db'})
+def run_conversion(config_name):
+ global_config, app_config = setup_global_and_app_config(config_name)
+ sql_conn, sql_db = sql_connect(app_config)
mk_conn, mk_db = mongo_connect(app_config)
Base.metadata.create_all(sql_db.engine)
@@ -165,4 +174,4 @@ def main():
if __name__ == '__main__':
- main()
+ run_conversion("mediagoblin.ini")
diff --git a/mediagoblin/db/sql/extratypes.py b/mediagoblin/db/sql/extratypes.py
index 3a594728..8e078f14 100644
--- a/mediagoblin/db/sql/extratypes.py
+++ b/mediagoblin/db/sql/extratypes.py
@@ -15,7 +15,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-from sqlalchemy.types import TypeDecorator, Unicode
+from sqlalchemy.types import TypeDecorator, Unicode, VARCHAR
+import json
class PathTupleWithSlashes(TypeDecorator):
@@ -35,3 +36,28 @@ class PathTupleWithSlashes(TypeDecorator):
if value is not None:
value = tuple(value.split('/'))
return value
+
+
+# The following class and only this one class is in very
+# large parts based on example code from sqlalchemy.
+#
+# The original copyright notice and license follows:
+# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+#
+class JSONEncoded(TypeDecorator):
+ "Represents an immutable structure as a json-encoded string."
+
+ impl = VARCHAR
+
+ def process_bind_param(self, value, dialect):
+ if value is not None:
+ value = json.dumps(value)
+ return value
+
+ def process_result_value(self, value, dialect):
+ if value is not None:
+ value = json.loads(value)
+ return value
diff --git a/mediagoblin/db/sql/migrations.py b/mediagoblin/db/sql/migrations.py
new file mode 100644
index 00000000..98d0d0aa
--- /dev/null
+++ b/mediagoblin/db/sql/migrations.py
@@ -0,0 +1,17 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011 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/>.
+
+MIGRATIONS = {}
diff --git a/mediagoblin/db/sql/models.py b/mediagoblin/db/sql/models.py
index 9d06f79c..dbc9ca05 100644
--- a/mediagoblin/db/sql/models.py
+++ b/mediagoblin/db/sql/models.py
@@ -29,9 +29,16 @@ from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.sql.expression import desc
from sqlalchemy.ext.associationproxy import association_proxy
-from mediagoblin.db.sql.extratypes import PathTupleWithSlashes
+from mediagoblin.db.sql.extratypes import PathTupleWithSlashes, JSONEncoded
from mediagoblin.db.sql.base import Base, DictReadAttrProxy
-from mediagoblin.db.mixin import UserMixin, MediaEntryMixin
+from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin
+
+# It's actually kind of annoying how sqlalchemy-migrate does this, if
+# I understand it right, but whatever. Anyway, don't remove this :P
+#
+# We could do migration calls more manually instead of relying on
+# this import-based meddling...
+from migrate import changeset
class SimpleFieldAlias(object):
@@ -64,7 +71,6 @@ class User(Base, UserMixin):
is_admin = Column(Boolean, default=False, nullable=False)
url = Column(Unicode)
bio = Column(UnicodeText) # ??
- bio_html = Column(UnicodeText) # ??
fp_verification_key = Column(Unicode)
fp_token_expire = Column(DateTime)
@@ -86,14 +92,13 @@ class MediaEntry(Base, MediaEntryMixin):
slug = Column(Unicode)
created = Column(DateTime, nullable=False, default=datetime.datetime.now)
description = Column(UnicodeText) # ??
- description_html = 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(UnicodeText)
+ fail_metadata = Column(JSONEncoded)
queued_media_file = Column(PathTupleWithSlashes)
@@ -113,6 +118,15 @@ class MediaEntry(Base, MediaEntryMixin):
creator=lambda k, v: MediaFile(name=k, file_path=v)
)
+ attachment_files_helper = relationship("MediaAttachmentFile",
+ cascade="all, delete-orphan",
+ order_by="MediaAttachmentFile.created"
+ )
+ attachment_files = association_proxy("attachment_files_helper", "dict_view",
+ creator=lambda v: MediaAttachmentFile(
+ name=v["name"], filepath=v["filepath"])
+ )
+
tags_helper = relationship("MediaTag",
cascade="all, delete-orphan"
)
@@ -122,7 +136,6 @@ class MediaEntry(Base, MediaEntryMixin):
## TODO
# media_data
- # attachment_files
# fail_error
_id = SimpleFieldAlias("id")
@@ -154,6 +167,15 @@ class MediaEntry(Base, MediaEntryMixin):
if media is not None:
return media.url_for_self(urlgen)
+ @property
+ def media_data(self):
+ # TODO: Replace with proper code to read the correct table
+ return {}
+
+ def media_data_init(self, **kwargs):
+ # TODO: Implement this
+ pass
+
class MediaFile(Base):
"""
@@ -172,6 +194,23 @@ class MediaFile(Base):
return "<MediaFile %s: %r>" % (self.name, self.file_path)
+class MediaAttachmentFile(Base):
+ __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)
+
+ @property
+ def dict_view(self):
+ """A dict like view on this object"""
+ return DictReadAttrProxy(self)
+
+
class Tag(Base):
__tablename__ = "tags"
@@ -209,10 +248,12 @@ class MediaTag(Base):
creator=Tag.find_or_new
)
- def __init__(self, name, slug):
+ def __init__(self, name=None, slug=None):
Base.__init__(self)
- self.name = name
- self.tag_helper = Tag.find_or_new(slug)
+ if name is not None:
+ self.name = name
+ if slug is not None:
+ self.tag_helper = Tag.find_or_new(slug)
@property
def dict_view(self):
@@ -220,7 +261,7 @@ class MediaTag(Base):
return DictReadAttrProxy(self)
-class MediaComment(Base):
+class MediaComment(Base, MediaCommentMixin):
__tablename__ = "media_comments"
id = Column(Integer, primary_key=True)
@@ -229,13 +270,32 @@ class MediaComment(Base):
author = Column(Integer, ForeignKey('users.id'), nullable=False)
created = Column(DateTime, nullable=False, default=datetime.datetime.now)
content = Column(UnicodeText, nullable=False)
- content_html = Column(UnicodeText)
get_author = relationship(User)
_id = SimpleFieldAlias("id")
+MODELS = [
+ User, MediaEntry, Tag, MediaTag, MediaComment]
+
+
+######################################################
+# 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):
+ __tablename__ = "migrations"
+
+ name = Column(Unicode, primary_key=True)
+ version = Column(Integer, nullable=False, default=0)
+
+######################################################
+
+
def show_table_init(engine_uri):
if engine_uri is None:
engine_uri = 'sqlite:///:memory:'
diff --git a/mediagoblin/db/sql/open.py b/mediagoblin/db/sql/open.py
index 1bfc5538..a8677bcb 100644
--- a/mediagoblin/db/sql/open.py
+++ b/mediagoblin/db/sql/open.py
@@ -36,6 +36,7 @@ class DatabaseMaster(object):
Session.flush()
def reset_after_request(self):
+ Session.rollback()
Session.remove()
diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/sql/util.py
new file mode 100644
index 00000000..13bc97e1
--- /dev/null
+++ b/mediagoblin/db/sql/util.py
@@ -0,0 +1,284 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import sys
+from mediagoblin.db.sql.base import Session
+
+
+def _simple_printer(string):
+ """
+ Prints a string, but without an auto \n at the end.
+ """
+ sys.stdout.write(string)
+ sys.stdout.flush()
+
+
+class MigrationManager(object):
+ """
+ Migration handling tool.
+
+ Takes information about a database, lets you update the database
+ to the latest migrations, etc.
+ """
+
+ def __init__(self, name, models, migration_registry, session,
+ printer=_simple_printer):
+ """
+ Args:
+ - name: identifier of this section of the database
+ - session: session we're going to migrate
+ - migration_registry: where we should find all migrations to
+ run
+ """
+ self.name = name
+ self.models = models
+ self.session = session
+ self.migration_registry = migration_registry
+ self._sorted_migrations = None
+ self.printer = printer
+
+ # For convenience
+ from mediagoblin.db.sql.models import MigrationData
+
+ self.migration_model = MigrationData
+ self.migration_table = MigrationData.__table__
+
+ @property
+ def sorted_migrations(self):
+ """
+ Sort migrations if necessary and store in self._sorted_migrations
+ """
+ if not self._sorted_migrations:
+ self._sorted_migrations = sorted(
+ self.migration_registry.items(),
+ # sort on the key... the migration number
+ key=lambda migration_tuple: migration_tuple[0])
+
+ return self._sorted_migrations
+
+ @property
+ def migration_data(self):
+ """
+ Get the migration row associated with this object, if any.
+ """
+ return self.session.query(
+ self.migration_model).filter_by(name=self.name).first()
+
+ @property
+ def latest_migration(self):
+ """
+ Return a migration number for the latest migration, or 0 if
+ there are no migrations.
+ """
+ if self.sorted_migrations:
+ return self.sorted_migrations[-1][0]
+ else:
+ # If no migrations have been set, we start at 0.
+ return 0
+
+ @property
+ def database_current_migration(self):
+ """
+ Return the current migration in the database.
+ """
+ # If the table doesn't even exist, return None.
+ if not self.migration_table.exists(self.session.bind):
+ return None
+
+ # Also return None if self.migration_data is None.
+ if self.migration_data is None:
+ return None
+
+ return self.migration_data.version
+
+ def set_current_migration(self, migration_number=None):
+ """
+ Set the migration in the database to migration_number
+ (or, the latest available)
+ """
+ self.migration_data.version = migration_number or self.latest_migration
+ self.session.commit()
+
+ def migrations_to_run(self):
+ """
+ Get a list of migrations to run still, if any.
+
+ Note that this will fail if there's no migration record for
+ this class!
+ """
+ assert self.database_current_migration is not None
+
+ db_current_migration = self.database_current_migration
+
+ return [
+ (migration_number, migration_func)
+ for migration_number, migration_func in self.sorted_migrations
+ if migration_number > db_current_migration]
+
+
+ def init_tables(self):
+ """
+ Create all tables relative to this package
+ """
+ # sanity check before we proceed, none of these should be created
+ for model in self.models:
+ # Maybe in the future just print out a "Yikes!" or something?
+ assert not model.__table__.exists(self.session.bind)
+
+ self.migration_model.metadata.create_all(
+ self.session.bind,
+ tables=[model.__table__ for model in self.models])
+
+ def create_new_migration_record(self):
+ """
+ Create a new migration record for this migration set
+ """
+ migration_record = self.migration_model(
+ name=self.name,
+ version=self.latest_migration)
+ self.session.add(migration_record)
+ self.session.commit()
+
+ def dry_run(self):
+ """
+ Print out a dry run of what we would have upgraded.
+ """
+ if self.database_current_migration is None:
+ self.printer(
+ u'~> Woulda initialized: %s\n' % self.name_for_printing())
+ return u'inited'
+
+ migrations_to_run = self.migrations_to_run()
+ if migrations_to_run:
+ self.printer(
+ u'~> Woulda updated %s:\n' % self.name_for_printing())
+
+ for migration_number, migration_func in migrations_to_run():
+ self.printer(
+ u' + Would update %s, "%s"\n' % (
+ migration_number, migration_func.func_name))
+
+ return u'migrated'
+
+ def name_for_printing(self):
+ if self.name == u'__main__':
+ return u"main mediagoblin tables"
+ else:
+ # TODO: Use the friendlier media manager "human readable" name
+ return u'media type "%s"' % self.name
+
+ def init_or_migrate(self):
+ """
+ Initialize the database or migrate if appropriate.
+
+ Returns information about whether or not we initialized
+ ('inited'), migrated ('migrated'), or did nothing (None)
+ """
+ assure_migrations_table_setup(self.session)
+
+ # Find out what migration number, if any, this database data is at,
+ # and what the latest is.
+ migration_number = self.database_current_migration
+
+ # Is this our first time? Is there even a table entry for
+ # this identifier?
+ # If so:
+ # - create all tables
+ # - create record in migrations registry
+ # - print / inform the user
+ # - return 'inited'
+ if migration_number is None:
+ self.printer(u"-> Initializing %s... " % self.name_for_printing())
+
+ self.init_tables()
+ # auto-set at latest migration number
+ self.create_new_migration_record()
+
+ self.printer(u"done.\n")
+ self.set_current_migration()
+ return u'inited'
+
+ # Run migrations, if appropriate.
+ migrations_to_run = self.migrations_to_run()
+ if migrations_to_run:
+ self.printer(
+ u'-> Updating %s:\n' % self.name_for_printing())
+ for migration_number, migration_func in migrations_to_run:
+ self.printer(
+ u' + Running migration %s, "%s"... ' % (
+ migration_number, migration_func.func_name))
+ migration_func(self.session)
+ self.printer('done.\n')
+
+ self.set_current_migration()
+ return u'migrated'
+
+ # Otherwise return None. Well it would do this anyway, but
+ # for clarity... ;)
+ return None
+
+
+class RegisterMigration(object):
+ """
+ Tool for registering migrations
+
+ Call like:
+
+ @RegisterMigration(33)
+ def update_dwarves(database):
+ [...]
+
+ This will register your migration with the default migration
+ registry. Alternately, to specify a very specific
+ migration_registry, you can pass in that as the second argument.
+
+ Note, the number of your migration should NEVER be 0 or less than
+ 0. 0 is the default "no migrations" state!
+ """
+ def __init__(self, migration_number, migration_registry):
+ assert migration_number > 0, "Migration number must be > 0!"
+ assert migration_number not in migration_registry, \
+ "Duplicate migration numbers detected! That's not allowed!"
+
+ self.migration_number = migration_number
+ self.migration_registry = migration_registry
+
+ def __call__(self, migration):
+ self.migration_registry[self.migration_number] = migration
+ return migration
+
+
+def assure_migrations_table_setup(db):
+ """
+ Make sure the migrations table is set up in the database.
+ """
+ from mediagoblin.db.sql.models import MigrationData
+
+ if not MigrationData.__table__.exists(db.bind):
+ MigrationData.metadata.create_all(
+ db.bind, tables=[MigrationData.__table__])
+
+
+##########################
+# Random utility functions
+##########################
+
+
+def atomic_update(table, query_dict, update_values):
+ table.find(query_dict).update(update_values,
+ synchronize_session=False)
+ Session.commit()
diff --git a/mediagoblin/db/util.py b/mediagoblin/db/util.py
index 1fc949a6..73aee238 100644
--- a/mediagoblin/db/util.py
+++ b/mediagoblin/db/util.py
@@ -21,5 +21,7 @@ except ImportError:
if use_sql:
from mediagoblin.db.sql.fake import ObjectId, InvalidId, DESCENDING
+ from mediagoblin.db.sql.util import atomic_update
else:
- from mediagoblin.db.mongo.util import ObjectId, InvalidId, DESCENDING
+ from mediagoblin.db.mongo.util import \
+ ObjectId, InvalidId, DESCENDING, atomic_update
diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py
index a7245517..d21ef03a 100644
--- a/mediagoblin/edit/views.py
+++ b/mediagoblin/edit/views.py
@@ -34,7 +34,7 @@ from mediagoblin.tools.response import render_to_response, redirect
from mediagoblin.tools.translate import pass_to_ugettext as _
from mediagoblin.tools.text import (
clean_html, convert_to_tag_list_of_dicts,
- media_tags_as_string, cleaned_markdown_conversion)
+ media_tags_as_string)
from mediagoblin.tools.licenses import SUPPORTED_LICENSES
@@ -72,9 +72,6 @@ def edit_media(request, media):
media.tags = convert_to_tag_list_of_dicts(
request.POST.get('tags'))
- media.description_html = cleaned_markdown_conversion(
- media.description)
-
media.license = unicode(request.POST.get('license', '')) or None
media.slug = unicode(request.POST['slug'])
@@ -123,7 +120,7 @@ def edit_attachments(request, media):
finally:
request.POST['attachment_file'].file.close()
- media['attachment_files'].append(dict(
+ media.attachment_files.append(dict(
name=request.POST['attachment_name'] \
or request.POST['attachment_file'].filename,
filepath=attachment_public_filepath,
@@ -171,8 +168,6 @@ def edit_profile(request):
user.url = unicode(request.POST['url'])
user.bio = unicode(request.POST['bio'])
- user.bio_html = cleaned_markdown_conversion(user.bio)
-
user.save()
messages.add_message(request,
diff --git a/mediagoblin/gmg_commands/__init__.py b/mediagoblin/gmg_commands/__init__.py
index db944b3c..054e2616 100644
--- a/mediagoblin/gmg_commands/__init__.py
+++ b/mediagoblin/gmg_commands/__init__.py
@@ -53,6 +53,14 @@ SUBCOMMAND_MAP = {
'setup': 'mediagoblin.gmg_commands.import_export:import_export_parse_setup',
'func': 'mediagoblin.gmg_commands.import_export:env_import',
'help': 'Exports the data for this MediaGoblin instance'},
+ 'dbupdate': {
+ 'setup': 'mediagoblin.gmg_commands.dbupdate:dbupdate_parse_setup',
+ 'func': 'mediagoblin.gmg_commands.dbupdate:dbupdate',
+ 'help': 'Set up or update the SQL database'},
+ 'convert_mongo_to_sql': {
+ 'setup': 'mediagoblin.gmg_commands.mongosql:mongosql_parser_setup',
+ 'func': 'mediagoblin.gmg_commands.mongosql:mongosql',
+ 'help': 'Convert Mongo DB data to SQL DB data'},
}
diff --git a/mediagoblin/gmg_commands/dbupdate.py b/mediagoblin/gmg_commands/dbupdate.py
new file mode 100644
index 00000000..27698170
--- /dev/null
+++ b/mediagoblin/gmg_commands/dbupdate.py
@@ -0,0 +1,89 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from sqlalchemy.orm import sessionmaker
+
+from mediagoblin.db.sql.open import setup_connection_and_db_from_config
+from mediagoblin.db.sql.util import (
+ MigrationManager, assure_migrations_table_setup)
+from mediagoblin.init import setup_global_and_app_config
+from mediagoblin.tools.common import import_component
+
+
+def dbupdate_parse_setup(subparser):
+ pass
+
+
+class DatabaseData(object):
+ def __init__(self, name, models, migrations):
+ self.name = name
+ self.models = models
+ self.migrations = migrations
+
+ def make_migration_manager(self, session):
+ return MigrationManager(
+ self.name, self.models, self.migrations, session)
+
+
+def gather_database_data(media_types):
+ """
+ Gather all database data relevant to the extensions we have
+ installed so we can do migrations and table initialization.
+
+ Returns a list of DatabaseData objects.
+ """
+ managed_dbdata = []
+
+ # Add main first
+ from mediagoblin.db.sql.models import MODELS as MAIN_MODELS
+ from mediagoblin.db.sql.migrations import MIGRATIONS as MAIN_MIGRATIONS
+
+ managed_dbdata.append(
+ DatabaseData(
+ '__main__', MAIN_MODELS, MAIN_MIGRATIONS))
+
+ # Then get all registered media managers (eventually, plugins)
+ for media_type in media_types:
+ models = import_component('%s.models:MODELS' % media_type)
+ migrations = import_component('%s.migrations:MIGRATIONS' % media_type)
+ managed_dbdata.append(
+ DatabaseData(media_type, models, migrations))
+
+ return managed_dbdata
+
+
+def dbupdate(args):
+ """
+ Initialize or migrate the database as specified by the config file.
+
+ Will also initialize or migrate all extensions (media types, and
+ in the future, plugins)
+ """
+ globa_config, app_config = setup_global_and_app_config(args.conf_file)
+
+ # Gather information from all media managers / projects
+ dbdatas = gather_database_data(app_config['media_types'])
+
+ # Set up the database
+ connection, db = setup_connection_and_db_from_config(app_config)
+
+ Session = sessionmaker(bind=db.engine)
+
+ # Setup media managers for all dbdata, run init/migrate and print info
+ # For each component, create/migrate tables
+ for dbdata in dbdatas:
+ migration_manager = dbdata.make_migration_manager(Session())
+ migration_manager.init_or_migrate()
diff --git a/mediagoblin/gmg_commands/mongosql.py b/mediagoblin/gmg_commands/mongosql.py
new file mode 100644
index 00000000..a25263e2
--- /dev/null
+++ b/mediagoblin/gmg_commands/mongosql.py
@@ -0,0 +1,25 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.db.sql.convert import run_conversion
+
+
+def mongosql_parser_setup(subparser):
+ pass
+
+
+def mongosql(args):
+ run_conversion(args.conf_file)
diff --git a/mediagoblin/i18n/en/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/en/LC_MESSAGES/mediagoblin.po
index b5832fe4..7c64c09f 100644
--- a/mediagoblin/i18n/en/LC_MESSAGES/mediagoblin.po
+++ b/mediagoblin/i18n/en/LC_MESSAGES/mediagoblin.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2012-02-09 09:30-0600\n"
+"POT-Creation-Date: 2012-02-26 15:51-0600\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -37,51 +37,51 @@ msgstr ""
msgid "Sorry, registration is disabled on this instance."
msgstr ""
-#: mediagoblin/auth/views.py:73
+#: mediagoblin/auth/views.py:75
msgid "Sorry, a user with that name already exists."
msgstr ""
-#: mediagoblin/auth/views.py:77
+#: mediagoblin/auth/views.py:79
msgid "Sorry, a user with that email address already exists."
msgstr ""
-#: mediagoblin/auth/views.py:180
+#: mediagoblin/auth/views.py:182
msgid ""
"Your email address has been verified. You may now login, edit your "
"profile, and submit images!"
msgstr ""
-#: mediagoblin/auth/views.py:186
+#: mediagoblin/auth/views.py:188
msgid "The verification key or user id is incorrect"
msgstr ""
-#: mediagoblin/auth/views.py:204
+#: mediagoblin/auth/views.py:206
msgid "You must be logged in so we know who to send the email to!"
msgstr ""
-#: mediagoblin/auth/views.py:212
+#: mediagoblin/auth/views.py:214
msgid "You've already verified your email address!"
msgstr ""
-#: mediagoblin/auth/views.py:225
+#: mediagoblin/auth/views.py:227
msgid "Resent your verification email."
msgstr ""
-#: mediagoblin/auth/views.py:260
+#: mediagoblin/auth/views.py:262
msgid "An email has been sent with instructions on how to change your password."
msgstr ""
-#: mediagoblin/auth/views.py:270
+#: mediagoblin/auth/views.py:272
msgid ""
"Could not send password recovery email as your username is inactive or "
"your account's email address has not been verified."
msgstr ""
-#: mediagoblin/auth/views.py:282
+#: mediagoblin/auth/views.py:284
msgid "Couldn't find someone with that username or email."
msgstr ""
-#: mediagoblin/auth/views.py:330
+#: mediagoblin/auth/views.py:332
msgid "You can now log in using your new password."
msgstr ""
@@ -152,23 +152,23 @@ msgstr ""
msgid "An entry with that slug already exists for this user."
msgstr ""
-#: mediagoblin/edit/views.py:92
+#: mediagoblin/edit/views.py:89
msgid "You are editing another user's media. Proceed with caution."
msgstr ""
-#: mediagoblin/edit/views.py:162
+#: mediagoblin/edit/views.py:159
msgid "You are editing a user's profile. Proceed with caution."
msgstr ""
-#: mediagoblin/edit/views.py:180
+#: mediagoblin/edit/views.py:175
msgid "Profile changes saved"
msgstr ""
-#: mediagoblin/edit/views.py:206
+#: mediagoblin/edit/views.py:201
msgid "Wrong password"
msgstr ""
-#: mediagoblin/edit/views.py:222
+#: mediagoblin/edit/views.py:217
msgid "Account settings saved"
msgstr ""
@@ -188,7 +188,7 @@ msgstr ""
msgid "You must provide a file."
msgstr ""
-#: mediagoblin/submit/views.py:158
+#: mediagoblin/submit/views.py:156
msgid "Woohoo! Submitted!"
msgstr ""
@@ -623,23 +623,23 @@ msgstr ""
msgid "I am sure I want to delete this"
msgstr ""
-#: mediagoblin/user_pages/views.py:155
+#: mediagoblin/user_pages/views.py:153
msgid "Oops, your comment was empty."
msgstr ""
-#: mediagoblin/user_pages/views.py:161
+#: mediagoblin/user_pages/views.py:159
msgid "Your comment has been posted!"
msgstr ""
-#: mediagoblin/user_pages/views.py:183
+#: mediagoblin/user_pages/views.py:181
msgid "You deleted the media."
msgstr ""
-#: mediagoblin/user_pages/views.py:190
+#: mediagoblin/user_pages/views.py:188
msgid "The media was not deleted because you didn't check that you were sure."
msgstr ""
-#: mediagoblin/user_pages/views.py:198
+#: mediagoblin/user_pages/views.py:196
msgid "You are about to delete another user's media. Proceed with caution."
msgstr ""
diff --git a/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.mo
index 25ab5836..f5a660d9 100644
--- a/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.mo
+++ b/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.po
index 49626556..b3088b25 100644
--- a/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.po
+++ b/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.po
@@ -10,8 +10,8 @@ msgid ""
msgstr ""
"Project-Id-Version: GNU MediaGoblin\n"
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
-"POT-Creation-Date: 2012-01-29 13:47-0600\n"
-"PO-Revision-Date: 2012-02-05 21:07+0000\n"
+"POT-Creation-Date: 2012-02-09 09:30-0600\n"
+"PO-Revision-Date: 2012-02-26 19:34+0000\n"
"Last-Translator: aleksejrs <deletesoftware@yandex.ru>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
@@ -21,7 +21,7 @@ msgstr ""
"Language: eo\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
-#: mediagoblin/processing.py:143
+#: mediagoblin/processing.py:153
msgid "Invalid file given for media type."
msgstr "La provizita dosiero ne konformas al la informtipo."
@@ -53,9 +53,7 @@ msgstr "Ni bedaÅ­ras, sed konto kun tiu retpoÅtadreso jam ekzistas."
msgid ""
"Your email address has been verified. You may now login, edit your profile, "
"and submit images!"
-msgstr ""
-"Via retpoÅtadreso estas konfirmita. Vi povas nun ensaluti, redakti vian "
-"profilon, kaj alÅuti bildojn!"
+msgstr "Via retpoÅtadreso estas konfirmita. Vi povas nun ensaluti, redakti vian profilon, kaj alÅuti bildojn!"
#: mediagoblin/auth/views.py:186
msgid "The verification key or user id is incorrect"
@@ -82,9 +80,7 @@ msgstr "Senditas retletero kun instrukcio pri kiel ÅanÄi vian pasvorton."
msgid ""
"Could not send password recovery email as your username is inactive or your "
"account's email address has not been verified."
-msgstr ""
-"Ni ne povas sendi pasvortsavan retleteron, ĉar aŭ via konto estas neaktiva, "
-"aÅ­ Äia retpoÅtadreso ne estis konfirmita."
+msgstr "Ni ne povas sendi pasvortsavan retleteron, ĉar aÅ­ via konto estas neaktiva, aÅ­ Äia retpoÅtadreso ne estis konfirmita."
#: mediagoblin/auth/views.py:282
msgid "Couldn't find someone with that username or email."
@@ -108,10 +104,7 @@ msgid ""
"You can use\n"
" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
" Markdown</a> for formatting."
-msgstr ""
-"Vi povas uzi por markado la lingvon\n"
-" «<a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
-" Markdown</a>»."
+msgstr "Vi povas uzi por markado la lingvon\n «<a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a>»."
#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
msgid "Tags"
@@ -133,9 +126,7 @@ msgstr "La distingiga adresparto ne povas esti malplena"
msgid ""
"The title part of this media's address. You usually don't need to change "
"this."
-msgstr ""
-"La dosiertitol-bazita parto de la dosieradreso. Ordinare ne necesas Äin "
-"ÅanÄi."
+msgstr "La dosiertitol-bazita parto de la dosieradreso. Ordinare ne necesas Äin ÅanÄi."
#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
msgid "License"
@@ -221,9 +212,7 @@ msgstr "VerÅajne ĉe ĉi tiu adreso ne estas paÄo. Ni bedaÅ­ras!"
msgid ""
"If you're sure the address is correct, maybe the page you're looking for has"
" been moved or deleted."
-msgstr ""
-"Se vi estas certa, ke la adreso estas Äusta, eble la serĉata de vi paÄo "
-"estis movita aÅ­ forigita."
+msgstr "Se vi estas certa, ke la adreso estas Äusta, eble la serĉata de vi paÄo estis movita aÅ­ forigita."
#: mediagoblin/templates/mediagoblin/base.html:46
msgid "MediaGoblin logo"
@@ -252,9 +241,7 @@ msgstr "Ensaluti"
msgid ""
"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a "
"href=\"http://gnu.org/\">GNU</a> project"
-msgstr ""
-"Funkcias per <a href=\"http://mediagoblin.org\">MediaGoblin</a>, unu el la "
-"<a href=\"http://gnu.org/\">projektoj de GNU</a>"
+msgstr "Funkcias per <a href=\"http://mediagoblin.org\">MediaGoblin</a>, unu el la <a href=\"http://gnu.org/\">projektoj de GNU</a>"
#: mediagoblin/templates/mediagoblin/root.html:24
msgid "Explore"
@@ -268,18 +255,13 @@ msgstr "Saluton, kaj bonvenon al ĉi tiu MediaGoblina retpaÄaro!"
msgid ""
"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
"extraordinarily great piece of media hosting software."
-msgstr ""
-"Ĉi tiu retpaÄaro funkcias per <a "
-"href=\"http://mediagoblin.org\">MediaGoblin</a>, eksterordinare bonega "
-"programaro por gastigado de aÅ­dâ€vidâ€dosieroj."
+msgstr "Ĉi tiu retpaÄaro funkcias per <a href=\"http://mediagoblin.org\">MediaGoblin</a>, eksterordinare bonega programaro por gastigado de aÅ­dâ€vidâ€dosieroj."
#: mediagoblin/templates/mediagoblin/root.html:29
msgid ""
"To add your own media, place comments, save your favourites and more, you "
"can log in with your MediaGoblin account."
-msgstr ""
-"Por aldoni viajn proprajn dosierojn, fari al vi liston de la plej plaĉaj, "
-"ks, vi povas ensaluti je via MediaGoblina konto."
+msgstr "Por aldoni viajn proprajn dosierojn, fari al vi liston de la plej plaĉaj, ks, vi povas ensaluti je via MediaGoblina konto."
#: mediagoblin/templates/mediagoblin/root.html:31
msgid "Don't have one yet? It's easy!"
@@ -291,10 +273,7 @@ msgid ""
"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
" or\n"
" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
-msgstr ""
-"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Kreu konton en ĉi tiu retejo</a>\n"
-" aÅ­\n"
-" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">ekfunkciigu MediaGoblin’on en via propra servilo</a>"
+msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Kreu konton en ĉi tiu retejo</a>\n aŭ\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">ekfunkciigu MediaGoblin’on en via propra servilo</a>"
#: mediagoblin/templates/mediagoblin/root.html:40
msgid "Most recent media"
@@ -328,14 +307,7 @@ msgid ""
"\n"
"If you think this is an error, just ignore this email and continue being\n"
"a happy goblin!"
-msgstr ""
-"Saluton, %(username)s,\n"
-"\n"
-"por ÅanÄi vian pasvorton ĉe GNUa MediaGoblin, sekvu la jenan retadreson per via TTT-legilo:\n"
-"\n"
-"%(verification_url)s\n"
-"\n"
-"Se vi pensas, ke ĉi tiu retletero estas sendita erare, simple ignoru Äin kaj plu restu feliĉa koboldo!"
+msgstr "Saluton, %(username)s,\n\npor ÅanÄi vian pasvorton ĉe GNUa MediaGoblin, sekvu la jenan retadreson per via TTT-legilo:\n\n%(verification_url)s\n\nSe vi pensas, ke ĉi tiu retletero estas sendita erare, simple ignoru Äin kaj plu restu feliĉa koboldo!"
#: mediagoblin/templates/mediagoblin/auth/login.html:30
msgid "Logging in failed!"
@@ -370,12 +342,7 @@ msgid ""
"your web browser:\n"
"\n"
"%(verification_url)s"
-msgstr ""
-"Sal %(username)s,\n"
-"\n"
-"por aktivigi vian GNU MediaGoblin konton, malfermu la sekvantan URLon en via retumilo:\n"
-"\n"
-"%(verification_url)s"
+msgstr "Sal %(username)s,\n\npor aktivigi vian GNU MediaGoblin konton, malfermu la sekvantan URLon en via retumilo:\n\n%(verification_url)s"
#: mediagoblin/templates/mediagoblin/edit/edit.html:29
#, python-format
@@ -419,20 +386,14 @@ msgid ""
"Sorry, this video will not work because \n"
"\t your web browser does not support HTML5 \n"
"\t video."
-msgstr ""
-"Bedaŭrinde ĉi tiu filmo ne spekteblas, ĉar\n"
-"<span class=\"whitespace other\" title=\"Tab\">»</span> via TTT-legilo ne subtenas montradon\n"
-"<span class=\"whitespace other\" title=\"Tab\">»</span> de filmoj laŭ HTML5."
+msgstr "Bedaŭrinde ĉi tiu filmo ne spekteblas, ĉar\n<span class=\"whitespace other\" title=\"Tab\">»</span> via TTT-legilo ne subtenas montradon\n<span class=\"whitespace other\" title=\"Tab\">»</span> de filmoj laŭ HTML5."
#: mediagoblin/templates/mediagoblin/media_displays/video.html:36
msgid ""
"You can get a modern web browser that \n"
"\t can play this video at <a href=\"http://getfirefox.com\">\n"
"\t http://getfirefox.com</a>!"
-msgstr ""
-"Vi povas akiri modernan TTT-legilon,\n"
-"<span class=\"whitespace other\" title=\"Tab\">»</span> kapablan montri ĉi tiun filmon, ĉe <a href=\"http://getfirefox.com\">\n"
-"<span class=\"whitespace other\" title=\"Tab\">»</span> http://getfirefox.com</a>!"
+msgstr "Vi povas akiri modernan TTT-legilon,\n<span class=\"whitespace other\" title=\"Tab\">»</span> kapablan montri ĉi tiun filmon, ĉe <a href=\"http://getfirefox.com\">\n<span class=\"whitespace other\" title=\"Tab\">»</span> http://getfirefox.com</a>!"
#: mediagoblin/templates/mediagoblin/submit/start.html:26
msgid "Add your media"
@@ -488,9 +449,7 @@ msgid ""
"You can use <a "
"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
" formatting."
-msgstr ""
-"Vi povas uzi por markado la lingvon «&lt;a "
-"href=\"http://daringfireball.net/projects/markdown/basics\"&gt;Markdown&lt;/a&gt;»."
+msgstr "Vi povas uzi por markado la lingvon «<a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a>»."
#: mediagoblin/templates/mediagoblin/user_pages/media.html:116
msgid "Add this comment"
@@ -503,8 +462,7 @@ msgstr "je"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:153
#, python-format
msgid "<p>â– Browsing media by <a href=\"%(user_url)s\">%(username)s</a></p>"
-msgstr ""
-"<p>â– Foliumado de dosieraro de <a href=\"%(user_url)s\">%(username)s</a></p>"
+msgstr "<p>â– Foliumado de dosieraro de <a href=\"%(user_url)s\">%(username)s</a></p>"
#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
#, python-format
@@ -522,9 +480,7 @@ msgstr "Kontrolejo pri dosierpreparado."
#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:25
msgid ""
"You can track the state of media being processed for your gallery here."
-msgstr ""
-"Ĉi tie vi povas informiÄi pri la stato de preparado de dosieroj por via "
-"galerio."
+msgstr "Ĉi tie vi povas informiÄi pri la stato de preparado de dosieroj por via galerio."
#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:28
msgid "Media in-processing"
@@ -560,8 +516,7 @@ msgstr "PreskaÅ­ finite! Restas nur validigi vian konton."
#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
msgid ""
"An email should arrive in a few moments with instructions on how to do so."
-msgstr ""
-"Post kelkaj momentoj devas veni retletero kun instrukcio pri kiel tion fari."
+msgstr "Post kelkaj momentoj devas veni retletero kun instrukcio pri kiel tion fari."
#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
msgid "In case it doesn't:"
@@ -575,18 +530,14 @@ msgstr "Resendi kontrolmesaÄon"
msgid ""
"Someone has registered an account with this username, but it still has to be"
" activated."
-msgstr ""
-"Iu registris konton kun tiu ĉi uzantonomo, sed Äi devas ankoraÅ­ esti "
-"aktivigita."
+msgstr "Iu registris konton kun tiu ĉi uzantonomo, sed Äi devas ankoraÅ­ esti aktivigita."
#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
#, python-format
msgid ""
"If you are that person but you've lost your verification email, you can <a "
"href=\"%(login_url)s\">log in</a> and resend it."
-msgstr ""
-"Se vi estas tiu sed vi perdis vian kontrolmesaÄon, vi povas <a "
-"href=\"%(login_url)s\">ensaluti</a> kaj resendi Äin."
+msgstr "Se vi estas tiu sed vi perdis vian kontrolmesaÄon, vi povas <a href=\"%(login_url)s\">ensaluti</a> kaj resendi Äin."
#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
msgid "Here's a spot to tell others about yourself."
@@ -614,8 +565,7 @@ msgstr "Rigardi ĉiujn dosierojn de %(username)s"
msgid ""
"This is where your media will appear, but you don't seem to have added "
"anything yet."
-msgstr ""
-"Äœuste ĉi tie aperos viaj dosieroj, sed vi Åajne ankoraÅ­ nenion alÅutis."
+msgstr "Äœuste ĉi tie aperos viaj dosieroj, sed vi Åajne ankoraÅ­ nenion alÅutis."
#: mediagoblin/templates/mediagoblin/user_pages/user.html:163
#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72
@@ -690,12 +640,8 @@ msgstr "Vi forigis la dosieron."
#: mediagoblin/user_pages/views.py:190
msgid "The media was not deleted because you didn't check that you were sure."
-msgstr ""
-"La dosiero ne estis forigita, ĉar vi ne konfirmis vian certecon per la "
-"markilo."
+msgstr "La dosiero ne estis forigita, ĉar vi ne konfirmis vian certecon per la markilo."
#: mediagoblin/user_pages/views.py:198
msgid "You are about to delete another user's media. Proceed with caution."
msgstr "Vi estas forigonta dosieron de alia uzanto. Estu singardema."
-
-
diff --git a/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.mo
index dd7735fd..eb6cc942 100644
--- a/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.mo
+++ b/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.po
index d895f3bf..ea9d1dc3 100644
--- a/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.po
+++ b/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.po
@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: GNU MediaGoblin\n"
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
-"POT-Creation-Date: 2012-01-29 13:47-0600\n"
-"PO-Revision-Date: 2012-02-05 21:04+0000\n"
+"POT-Creation-Date: 2012-02-09 09:30-0600\n"
+"PO-Revision-Date: 2012-02-26 19:33+0000\n"
"Last-Translator: aleksejrs <deletesoftware@yandex.ru>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
@@ -19,7 +19,7 @@ msgstr ""
"Language: ru\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
-#: mediagoblin/processing.py:143
+#: mediagoblin/processing.py:153
msgid "Invalid file given for media type."
msgstr "Ðеправильный формат файла."
@@ -45,17 +45,13 @@ msgstr "Извините, пользователь Ñ Ñтим именем уж
#: mediagoblin/auth/views.py:77
msgid "Sorry, a user with that email address already exists."
-msgstr ""
-"Сожалеем, но на Ñтот Ð°Ð´Ñ€ÐµÑ Ñлектронной почты уже зарегиÑтрирована Ð´Ñ€ÑƒÐ³Ð°Ñ "
-"ÑƒÑ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ."
+msgstr "Сожалеем, но на Ñтот Ð°Ð´Ñ€ÐµÑ Ñлектронной почты уже зарегиÑтрирована Ð´Ñ€ÑƒÐ³Ð°Ñ ÑƒÑ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ."
#: mediagoblin/auth/views.py:180
msgid ""
"Your email address has been verified. You may now login, edit your profile, "
"and submit images!"
-msgstr ""
-"ÐÐ´Ñ€ÐµÑ Ð²Ð°ÑˆÐµÐ¹ Ñлектронной потвержден. Ð’Ñ‹ теперь можете войти и начать "
-"редактировать Ñвой профиль и загружать новые изображениÑ!"
+msgstr "ÐÐ´Ñ€ÐµÑ Ð²Ð°ÑˆÐµÐ¹ Ñлектронной потвержден. Ð’Ñ‹ теперь можете войти и начать редактировать Ñвой профиль и загружать новые изображениÑ!"
#: mediagoblin/auth/views.py:186
msgid "The verification key or user id is incorrect"
@@ -82,15 +78,11 @@ msgstr "Вам отправлено Ñлектронное пиÑьмо Ñ Ð¸Ð½Ñ
msgid ""
"Could not send password recovery email as your username is inactive or your "
"account's email address has not been verified."
-msgstr ""
-"Мы не можем отправить Ñообщение Ð´Ð»Ñ Ð²Ð¾ÑÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ, потому что ваша "
-"ÑƒÑ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ неактивна, либо указанный в ней Ð°Ð´Ñ€ÐµÑ Ñлектронной почты не "
-"был подтверждён."
+msgstr "Мы не можем отправить Ñообщение Ð´Ð»Ñ Ð²Ð¾ÑÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ, потому что ваша ÑƒÑ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ неактивна, либо указанный в ней Ð°Ð´Ñ€ÐµÑ Ñлектронной почты не был подтверждён."
#: mediagoblin/auth/views.py:282
msgid "Couldn't find someone with that username or email."
-msgstr ""
-"Ðе найдено никого Ñ Ñ‚Ð°ÐºÐ¸Ð¼ именем Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð»Ð¸ адреÑом Ñлектронной почты."
+msgstr "Ðе найдено никого Ñ Ñ‚Ð°ÐºÐ¸Ð¼ именем Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð»Ð¸ адреÑом Ñлектронной почты."
#: mediagoblin/auth/views.py:330
msgid "You can now log in using your new password."
@@ -110,10 +102,7 @@ msgid ""
"You can use\n"
" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
" Markdown</a> for formatting."
-msgstr ""
-"Ð”Ð»Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‚ÐºÐ¸ можете иÑпользовать Ñзык\n"
-" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
-" Markdown</a>."
+msgstr "Ð”Ð»Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‚ÐºÐ¸ можете иÑпользовать Ñзык\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a>."
#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
msgid "Tags"
@@ -135,9 +124,7 @@ msgstr "ÐžÑ‚Ð»Ð¸Ñ‡Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ñ‡Ð°Ñть адреÑа необходима"
msgid ""
"The title part of this media's address. You usually don't need to change "
"this."
-msgstr ""
-"ЧаÑть адреÑа Ñтого файла, Ð¿Ñ€Ð¾Ð¸Ð·Ð²Ð¾Ð´Ð½Ð°Ñ Ð¾Ñ‚ его названиÑ. Её обычно не "
-"требуетÑÑ Ð¸Ð·Ð¼ÐµÐ½Ñть."
+msgstr "ЧаÑть адреÑа Ñтого файла, Ð¿Ñ€Ð¾Ð¸Ð·Ð²Ð¾Ð´Ð½Ð°Ñ Ð¾Ñ‚ его названиÑ. Её обычно не требуетÑÑ Ð¸Ð·Ð¼ÐµÐ½Ñть."
#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
msgid "License"
@@ -157,9 +144,7 @@ msgstr "Старый пароль"
#: mediagoblin/edit/forms.py:65
msgid "Enter your old password to prove you own this account."
-msgstr ""
-"Введите Ñвой Ñтарый пароль в качеÑтве доказательÑтва, что Ñто ваша ÑƒÑ‡Ñ‘Ñ‚Ð½Ð°Ñ "
-"запиÑÑŒ."
+msgstr "Введите Ñвой Ñтарый пароль в качеÑтве доказательÑтва, что Ñто ваша ÑƒÑ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ."
#: mediagoblin/edit/forms.py:68
msgid "New password"
@@ -167,8 +152,7 @@ msgstr "Ðовый пароль"
#: mediagoblin/edit/views.py:68
msgid "An entry with that slug already exists for this user."
-msgstr ""
-"У Ñтого Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ ÑƒÐ¶Ðµ еÑть файл Ñ Ñ‚Ð°ÐºÐ¾Ð¹ отличительной чаÑтью адреÑа."
+msgstr "У Ñтого Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ ÑƒÐ¶Ðµ еÑть файл Ñ Ñ‚Ð°ÐºÐ¾Ð¹ отличительной чаÑтью адреÑа."
#: mediagoblin/edit/views.py:92
msgid "You are editing another user's media. Proceed with caution."
@@ -255,9 +239,7 @@ msgstr "Войти"
msgid ""
"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a "
"href=\"http://gnu.org/\">GNU</a> project"
-msgstr ""
-"Работает на <a href=\"http://mediagoblin.org\">MediaGoblin</a>, проекте <a "
-"href=\"http://gnu.org/\">GNU</a>"
+msgstr "Работает на <a href=\"http://mediagoblin.org\">MediaGoblin</a>, проекте <a href=\"http://gnu.org/\">GNU</a>"
#: mediagoblin/templates/mediagoblin/root.html:24
msgid "Explore"
@@ -271,18 +253,13 @@ msgstr "Привет! Добро пожаловать на наш MediaGoblin’
msgid ""
"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
"extraordinarily great piece of media hosting software."
-msgstr ""
-"Этот Ñайт работает на <a href=\"http://mediagoblin.org\">MediaGoblin</a>, "
-"необыкновенно замечательном ПО Ð´Ð»Ñ Ñ…Ð¾Ñтинга мультимедийных файлов."
+msgstr "Этот Ñайт работает на <a href=\"http://mediagoblin.org\">MediaGoblin</a>, необыкновенно замечательном ПО Ð´Ð»Ñ Ñ…Ð¾Ñтинга мультимедийных файлов."
#: mediagoblin/templates/mediagoblin/root.html:29
msgid ""
"To add your own media, place comments, save your favourites and more, you "
"can log in with your MediaGoblin account."
-msgstr ""
-"Ð”Ð»Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÑобÑтвенных файлов, комментированиÑ, Ð²ÐµÐ´ÐµÐ½Ð¸Ñ ÑпиÑка любимых "
-"файлов и Ñ‚. п. вы можете предÑтавитьÑÑ Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ вашей MediaGoblin’овой "
-"учётной запиÑи."
+msgstr "Ð”Ð»Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÑобÑтвенных файлов, комментированиÑ, Ð²ÐµÐ´ÐµÐ½Ð¸Ñ ÑпиÑка любимых файлов и Ñ‚. п. вы можете предÑтавитьÑÑ Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ вашей MediaGoblin’овой учётной запиÑи."
#: mediagoblin/templates/mediagoblin/root.html:31
msgid "Don't have one yet? It's easy!"
@@ -294,10 +271,7 @@ msgid ""
"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
" or\n"
" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
-msgstr ""
-"&lt;a class=\"button_action_highlight\" href=\"%(register_url)s\"&gt;Создайте учётную запиÑÑŒ на Ñтом Ñайте&lt;/a&gt;\n"
-" или\n"
-" &lt;a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\"&gt;уÑтановите MediaGoblin на ÑобÑтвенный Ñервер&lt;/a&gt;"
+msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Создайте учётную запиÑÑŒ на Ñтом Ñайте</a>\n или\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">уÑтановите MediaGoblin на ÑобÑтвенный Ñервер</a>"
#: mediagoblin/templates/mediagoblin/root.html:40
msgid "Most recent media"
@@ -331,16 +305,7 @@ msgid ""
"\n"
"If you think this is an error, just ignore this email and continue being\n"
"a happy goblin!"
-msgstr ""
-"Привет, %(username)s,\n"
-"\n"
-"чтобы Ñменить Ñвой пароль от GNU MediaGoblin, откройте\n"
-"Ñледующий URL вашим вебâ€Ð±Ñ€Ð°ÑƒÐ·ÐµÑ€Ð¾Ð¼:\n"
-"\n"
-"%(verification_url)s\n"
-"\n"
-"ЕÑли вы думаете, что Ñто какаÑâ€Ñ‚о ошибка, то игнорируйте\n"
-"Ñто Ñообщение и продолжайте быть ÑчаÑтливым гоблином!"
+msgstr "Привет, %(username)s,\n\nчтобы Ñменить Ñвой пароль от GNU MediaGoblin, откройте\nÑледующий URL вашим вебâ€Ð±Ñ€Ð°ÑƒÐ·ÐµÑ€Ð¾Ð¼:\n\n%(verification_url)s\n\nЕÑли вы думаете, что Ñто какаÑâ€Ñ‚о ошибка, то игнорируйте\nÑто Ñообщение и продолжайте быть ÑчаÑтливым гоблином!"
#: mediagoblin/templates/mediagoblin/auth/login.html:30
msgid "Logging in failed!"
@@ -375,12 +340,7 @@ msgid ""
"your web browser:\n"
"\n"
"%(verification_url)s"
-msgstr ""
-"Привет, %(username)s!\n"
-"\n"
-"Чтобы активировать Ñвой аккаунт в GNU MediaGoblin, откройте в Ñвоём вебâ€Ð±Ñ€Ð°ÑƒÐ·ÐµÑ€Ðµ Ñледующую ÑÑылку:\n"
-"\n"
-"%(verification_url)s"
+msgstr "Привет, %(username)s!\n\nЧтобы активировать Ñвой аккаунт в GNU MediaGoblin, откройте в Ñвоём вебâ€Ð±Ñ€Ð°ÑƒÐ·ÐµÑ€Ðµ Ñледующую ÑÑылку:\n\n%(verification_url)s"
#: mediagoblin/templates/mediagoblin/edit/edit.html:29
#, python-format
@@ -424,20 +384,14 @@ msgid ""
"Sorry, this video will not work because \n"
"\t your web browser does not support HTML5 \n"
"\t video."
-msgstr ""
-"Сожалеем, Ñтот ролик не проиграетÑÑ, âŽ\n"
-"» потому что ваш браузер не поддерживает âŽ\n"
-"» видео в ÑоответÑтвии Ñо Ñтандартом HTML5."
+msgstr "Сожалеем, Ñтот ролик не проиграетÑÑ, âŽ\n» потому что ваш браузер не поддерживает âŽ\n» видео в ÑоответÑтвии Ñо Ñтандартом HTML5."
#: mediagoblin/templates/mediagoblin/media_displays/video.html:36
msgid ""
"You can get a modern web browser that \n"
"\t can play this video at <a href=\"http://getfirefox.com\">\n"
"\t http://getfirefox.com</a>!"
-msgstr ""
-"Ð’Ñ‹ можете Ñкачать Ñовременный браузер,\n"
-"<span class=\"whitespace other\" title=\"Tab\">»</span> ÑпоÑобный воÑпроизводить Ñто видео, Ñ <a href=\"http://getfirefox.com\">\n"
-"<span class=\"whitespace other\" title=\"Tab\">»</span> http://getfirefox.com</a>!"
+msgstr "Ð’Ñ‹ можете Ñкачать Ñовременный браузер,\n<span class=\"whitespace other\" title=\"Tab\">»</span> ÑпоÑобный воÑпроизводить Ñто видео, Ñ <a href=\"http://getfirefox.com\">\n<span class=\"whitespace other\" title=\"Tab\">»</span> http://getfirefox.com</a>!"
#: mediagoblin/templates/mediagoblin/submit/start.html:26
msgid "Add your media"
@@ -493,9 +447,7 @@ msgid ""
"You can use <a "
"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
" formatting."
-msgstr ""
-"Ð”Ð»Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‚ÐºÐ¸ можете иÑпользовать Ñзык <a "
-"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a>."
+msgstr "Ð”Ð»Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‚ÐºÐ¸ можете иÑпользовать Ñзык <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a>."
#: mediagoblin/templates/mediagoblin/user_pages/media.html:116
msgid "Add this comment"
@@ -508,9 +460,7 @@ msgstr "в"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:153
#, python-format
msgid "<p>â– Browsing media by <a href=\"%(user_url)s\">%(username)s</a></p>"
-msgstr ""
-"<p>■ПроÑмотр файлов Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ <a "
-"href=\"%(user_url)s\">%(username)s</a></p>"
+msgstr "<p>■ПроÑмотр файлов Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ <a href=\"%(user_url)s\">%(username)s</a></p>"
#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
#, python-format
@@ -528,8 +478,7 @@ msgstr "Панель обработки файлов"
#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:25
msgid ""
"You can track the state of media being processed for your gallery here."
-msgstr ""
-"Ð’Ñ‹ можете Ñледить за ÑтатуÑом обработки файлов Ð´Ð»Ñ Ð²Ð°ÑˆÐµÐ¹ галереи здеÑÑŒ."
+msgstr "Ð’Ñ‹ можете Ñледить за ÑтатуÑом обработки файлов Ð´Ð»Ñ Ð²Ð°ÑˆÐµÐ¹ галереи здеÑÑŒ."
#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:28
msgid "Media in-processing"
@@ -565,9 +514,7 @@ msgstr "Почти закончили! Теперь надо активировÐ
#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
msgid ""
"An email should arrive in a few moments with instructions on how to do so."
-msgstr ""
-"Через пару мгновений на Ð°Ð´Ñ€ÐµÑ Ð²Ð°ÑˆÐµÐ¹ Ñлектронной почты должно прийти "
-"Ñообщение Ñ Ð´Ð°Ð»ÑŒÐ½ÐµÐ¹ÑˆÐ¸Ð¼Ð¸ инÑтрукциÑми."
+msgstr "Через пару мгновений на Ð°Ð´Ñ€ÐµÑ Ð²Ð°ÑˆÐµÐ¹ Ñлектронной почты должно прийти Ñообщение Ñ Ð´Ð°Ð»ÑŒÐ½ÐµÐ¹ÑˆÐ¸Ð¼Ð¸ инÑтрукциÑми."
#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
msgid "In case it doesn't:"
@@ -575,8 +522,7 @@ msgstr "РеÑли нет, то:"
#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
msgid "Resend verification email"
-msgstr ""
-"Повторно отправить Ñообщение Ð´Ð»Ñ Ð¿Ð¾Ð´Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð°Ð´Ñ€ÐµÑа Ñлектронной почты"
+msgstr "Повторно отправить Ñообщение Ð´Ð»Ñ Ð¿Ð¾Ð´Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð°Ð´Ñ€ÐµÑа Ñлектронной почты"
#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
msgid ""
@@ -589,9 +535,7 @@ msgstr "Ктоâ€Ñ‚о Ñоздал аккаунт Ñ Ñтим именем, но
msgid ""
"If you are that person but you've lost your verification email, you can <a "
"href=\"%(login_url)s\">log in</a> and resend it."
-msgstr ""
-"ЕÑли Ñто были вы, и еÑли вы потерÑли Ñообщение Ð´Ð»Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð°ÐºÐºÐ°ÑƒÐ½Ñ‚Ð°, "
-"то вы можете <a href=\"%(login_url)s\">войти</a> и отправить его повторно."
+msgstr "ЕÑли Ñто были вы, и еÑли вы потерÑли Ñообщение Ð´Ð»Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð°ÐºÐºÐ°ÑƒÐ½Ñ‚Ð°, то вы можете <a href=\"%(login_url)s\">войти</a> и отправить его повторно."
#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
msgid "Here's a spot to tell others about yourself."
@@ -699,5 +643,3 @@ msgstr "Файл не удалён, так как вы не подтвердил
#: mediagoblin/user_pages/views.py:198
msgid "You are about to delete another user's media. Proceed with caution."
msgstr "Ð’Ñ‹ на пороге ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° другого пользователÑ. Будьте оÑторожны."
-
-
diff --git a/mediagoblin/init/celery/__init__.py b/mediagoblin/init/celery/__init__.py
index fb958909..29ccd83a 100644
--- a/mediagoblin/init/celery/__init__.py
+++ b/mediagoblin/init/celery/__init__.py
@@ -47,30 +47,12 @@ def setup_celery_from_config(app_config, global_config,
celery_settings = {}
- # set up mongodb stuff
- celery_settings['CELERY_RESULT_BACKEND'] = 'mongodb'
- if 'BROKER_BACKEND' not in celery_settings:
- celery_settings['BROKER_BACKEND'] = 'mongodb'
-
- celery_mongo_settings = {}
-
- if 'db_host' in app_config:
- celery_mongo_settings['host'] = app_config['db_host']
- if celery_settings['BROKER_BACKEND'] == 'mongodb':
- celery_settings['BROKER_HOST'] = app_config['db_host']
- if 'db_port' in app_config:
- celery_mongo_settings['port'] = app_config['db_port']
- if celery_settings['BROKER_BACKEND'] == 'mongodb':
- celery_settings['BROKER_PORT'] = app_config['db_port']
- celery_mongo_settings['database'] = app_config['db_name']
-
- celery_settings['CELERY_MONGODB_BACKEND_SETTINGS'] = celery_mongo_settings
-
- # Add anything else
+ # Add all celery settings from config
for key, value in celery_conf.iteritems():
- key = key.upper()
celery_settings[key] = value
+ # TODO: use default result stuff here if it exists
+
# add mandatory celery imports
celery_imports = celery_settings.setdefault('CELERY_IMPORTS', [])
celery_imports.extend(MANDATORY_CELERY_IMPORTS)
diff --git a/mediagoblin/listings/views.py b/mediagoblin/listings/views.py
index 48320cb2..ba23fc46 100644
--- a/mediagoblin/listings/views.py
+++ b/mediagoblin/listings/views.py
@@ -91,7 +91,7 @@ def tag_atom_feed(request):
'type': 'text/html'}])
for entry in cursor:
feed.add(entry.get('title'),
- entry.get('description_html'),
+ entry.description_html,
id=entry.url_for_self(request.urlgen,qualified=True),
content_type='html',
author={'name': entry.get_uploader.username,
diff --git a/mediagoblin/media_types/ascii/migrations.py b/mediagoblin/media_types/ascii/migrations.py
new file mode 100644
index 00000000..f54c23ea
--- /dev/null
+++ b/mediagoblin/media_types/ascii/migrations.py
@@ -0,0 +1,17 @@
+# 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/>.
+
+MIGRATIONS = {}
diff --git a/mediagoblin/media_types/ascii/models.py b/mediagoblin/media_types/ascii/models.py
new file mode 100644
index 00000000..324794b9
--- /dev/null
+++ b/mediagoblin/media_types/ascii/models.py
@@ -0,0 +1,34 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+from mediagoblin.db.sql.models import Base
+
+from sqlalchemy import (
+ Column, Integer, Unicode, UnicodeText, DateTime, Boolean, ForeignKey,
+ UniqueConstraint)
+
+
+class AsciiData(Base):
+ __tablename__ = "ascii_data"
+
+ id = Column(Integer, primary_key=True)
+ media_entry = Column(
+ Integer, ForeignKey('media_entries.id'), nullable=False)
+
+
+DATA_MODEL = AsciiData
+MODELS = [AsciiData]
diff --git a/mediagoblin/media_types/image/migrations.py b/mediagoblin/media_types/image/migrations.py
new file mode 100644
index 00000000..f54c23ea
--- /dev/null
+++ b/mediagoblin/media_types/image/migrations.py
@@ -0,0 +1,17 @@
+# 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/>.
+
+MIGRATIONS = {}
diff --git a/mediagoblin/media_types/image/models.py b/mediagoblin/media_types/image/models.py
new file mode 100644
index 00000000..296eca0a
--- /dev/null
+++ b/mediagoblin/media_types/image/models.py
@@ -0,0 +1,19 @@
+from mediagoblin.db.sql.models import Base
+
+from sqlalchemy import (
+ Column, Integer, Unicode, UnicodeText, DateTime, Boolean, ForeignKey,
+ UniqueConstraint)
+
+
+class ImageData(Base):
+ __tablename__ = "image_data"
+
+ id = Column(Integer, primary_key=True)
+ width = Column(Integer)
+ height = Column(Integer)
+ media_entry = Column(
+ Integer, ForeignKey('media_entries.id'), nullable=False)
+
+
+DATA_MODEL = ImageData
+MODELS = [ImageData]
diff --git a/mediagoblin/media_types/video/migrations.py b/mediagoblin/media_types/video/migrations.py
new file mode 100644
index 00000000..f54c23ea
--- /dev/null
+++ b/mediagoblin/media_types/video/migrations.py
@@ -0,0 +1,17 @@
+# 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/>.
+
+MIGRATIONS = {}
diff --git a/mediagoblin/media_types/video/models.py b/mediagoblin/media_types/video/models.py
new file mode 100644
index 00000000..709d7910
--- /dev/null
+++ b/mediagoblin/media_types/video/models.py
@@ -0,0 +1,35 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+from mediagoblin.db.sql.models import Base
+
+from sqlalchemy import (
+ Column, Integer, SmallInteger, ForeignKey)
+
+
+class VideoData(Base):
+ __tablename__ = "video_data"
+
+ # The primary key *and* reference to the main media_entry
+ media_entry = Column(Integer, ForeignKey('media_entries.id'),
+ primary_key=True)
+ width = Column(SmallInteger)
+ height = Column(SmallInteger)
+
+
+DATA_MODEL = VideoData
+MODELS = [VideoData]
diff --git a/mediagoblin/media_types/video/processing.py b/mediagoblin/media_types/video/processing.py
index 9dc23c55..3a479802 100644
--- a/mediagoblin/media_types/video/processing.py
+++ b/mediagoblin/media_types/video/processing.py
@@ -77,9 +77,9 @@ def process_video(entry):
entry.media_files['webm_640'] = medium_filepath
# Save the width and height of the transcoded video
- entry.media_data['video'] = {
- u'width': transcoder.dst_data.videowidth,
- u'height': transcoder.dst_data.videoheight}
+ entry.media_data_init(
+ width=transcoder.dst_data.videowidth,
+ height=transcoder.dst_data.videoheight)
# Create a temporary file for the video thumbnail
tmp_thumb = tempfile.NamedTemporaryFile()
diff --git a/mediagoblin/processing.py b/mediagoblin/processing.py
index 9e57380d..1c84c557 100644
--- a/mediagoblin/processing.py
+++ b/mediagoblin/processing.py
@@ -18,7 +18,7 @@ import logging
from celery.task import Task
-from mediagoblin.db.util import ObjectId
+from mediagoblin.db.util import ObjectId, atomic_update
from mediagoblin import mg_globals as mgg
from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
@@ -108,21 +108,22 @@ def mark_entry_failed(entry_id, exc):
if isinstance(exc, BaseProcessingFail):
# Looks like yes, so record information about that failure and any
# metadata the user might have supplied.
- mgg.database['media_entries'].update(
+ atomic_update(mgg.database.MediaEntry,
{'_id': entry_id},
- {'$set': {u'state': u'failed',
- u'fail_error': exc.exception_path,
- u'fail_metadata': exc.metadata}})
+ {u'state': u'failed',
+ u'fail_error': exc.exception_path,
+ u'fail_metadata': exc.metadata})
else:
+ _log.warn("No idea what happened here, but it failed: %r", exc)
# Looks like no, so just mark it as failed and don't record a
# failure_error (we'll assume it wasn't handled) and don't record
# metadata (in fact overwrite it if somehow it had previous info
# here)
- mgg.database['media_entries'].update(
+ atomic_update(mgg.database.MediaEntry,
{'_id': entry_id},
- {'$set': {u'state': u'failed',
- u'fail_error': None,
- u'fail_metadata': {}}})
+ {u'state': u'failed',
+ u'fail_error': None,
+ u'fail_metadata': {}})
class BaseProcessingFail(Exception):
diff --git a/mediagoblin/static/images/goblin.ico b/mediagoblin/static/images/goblin.ico
index f2e7152f..ae5a1b12 100644
--- a/mediagoblin/static/images/goblin.ico
+++ b/mediagoblin/static/images/goblin.ico
Binary files differ
diff --git a/mediagoblin/static/images/goblin.png b/mediagoblin/static/images/goblin.png
index 0a3ad22e..672ed61a 100644
--- a/mediagoblin/static/images/goblin.png
+++ b/mediagoblin/static/images/goblin.png
Binary files differ
diff --git a/mediagoblin/submit/views.py b/mediagoblin/submit/views.py
index cdd097ec..1e145e9d 100644
--- a/mediagoblin/submit/views.py
+++ b/mediagoblin/submit/views.py
@@ -28,7 +28,7 @@ _log = logging.getLogger(__name__)
from werkzeug.utils import secure_filename
from mediagoblin.db.util import ObjectId
-from mediagoblin.tools.text import cleaned_markdown_conversion, convert_to_tag_list_of_dicts
+from mediagoblin.tools.text import convert_to_tag_list_of_dicts
from mediagoblin.tools.translate import pass_to_ugettext as _
from mediagoblin.tools.response import render_to_response, redirect
from mediagoblin.decorators import require_active_login
@@ -59,15 +59,13 @@ def submit_start(request):
# create entry and save in database
entry = request.db.MediaEntry()
- entry['_id'] = ObjectId()
+ entry.id = ObjectId()
entry.media_type = unicode(media_type)
entry.title = (
unicode(request.POST['title'])
or unicode(splitext(filename)[0]))
entry.description = unicode(request.POST.get('description'))
- entry.description_html = cleaned_markdown_conversion(
- entry.description)
entry.license = unicode(request.POST.get('license', "")) or None
@@ -80,11 +78,18 @@ def submit_start(request):
# Generate a slug from the title
entry.generate_slug()
+ # We generate this ourselves so we know what the taks id is for
+ # retrieval later.
+
+ # (If we got it off the task's auto-generation, there'd be
+ # a risk of a race condition when we'd save after sending
+ # off the task)
+ task_id = unicode(uuid.uuid4())
# Now store generate the queueing related filename
queue_filepath = request.app.queue_store.get_unique_filepath(
['media_entries',
- unicode(entry._id),
+ task_id,
secure_filename(filename)])
# queue appropriately
@@ -97,14 +102,7 @@ def submit_start(request):
# Add queued filename to the entry
entry.queued_media_file = queue_filepath
- # We generate this ourselves so we know what the taks id is for
- # retrieval later.
-
- # (If we got it off the task's auto-generation, there'd be
- # a risk of a race condition when we'd save after sending
- # off the task)
- task_id = unicode(uuid.uuid4())
- entry['queued_task_id'] = task_id
+ entry.queued_task_id = task_id
# Save now so we have this data before kicking off processing
entry.save(validate=True)
diff --git a/mediagoblin/templates/mediagoblin/media_displays/video.html b/mediagoblin/templates/mediagoblin/media_displays/video.html
index ec4338fa..acd570e7 100644
--- a/mediagoblin/templates/mediagoblin/media_displays/video.html
+++ b/mediagoblin/templates/mediagoblin/media_displays/video.html
@@ -21,8 +21,8 @@
{% block mediagoblin_media %}
<div class="video-player" style="position: relative;">
<video class="video-js vjs-default-skin"
- width="{{ media.media_data.video.width }}"
- height="{{ media.media_data.video.height }}"
+ width="{{ media.media_data.width }}"
+ height="{{ media.media_data.height }}"
controls="controls"
preload="auto"
data-setup="">
diff --git a/mediagoblin/tests/fake_carrot_conf_bad.ini b/mediagoblin/tests/fake_carrot_conf_bad.ini
index 0c79b354..9d8cf518 100644
--- a/mediagoblin/tests/fake_carrot_conf_bad.ini
+++ b/mediagoblin/tests/fake_carrot_conf_bad.ini
@@ -11,4 +11,4 @@ encouragement_phrase = 586956856856 # shouldn't throw error
blah_blah = "blah!" # shouldn't throw error either
[celery]
-eat_celery_with_carrots = pants # yeah that's def an error right there.
+EAT_CELERY_WITH_CARROTS = pants # yeah that's def an error right there.
diff --git a/mediagoblin/tests/fake_carrot_conf_good.ini b/mediagoblin/tests/fake_carrot_conf_good.ini
index fed14d07..1377907b 100644
--- a/mediagoblin/tests/fake_carrot_conf_good.ini
+++ b/mediagoblin/tests/fake_carrot_conf_good.ini
@@ -10,4 +10,4 @@ encouragement_phrase = "I'd love it if you eat your carrots!"
blah_blah = "blah!"
[celery]
-eat_celery_with_carrots = False
+EAT_CELERY_WITH_CARROTS = False
diff --git a/mediagoblin/tests/fake_celery_conf.ini b/mediagoblin/tests/fake_celery_conf.ini
index 3e52ac3a..67b0cba6 100644
--- a/mediagoblin/tests/fake_celery_conf.ini
+++ b/mediagoblin/tests/fake_celery_conf.ini
@@ -1,9 +1,9 @@
-['mediagoblin']
+[mediagoblin]
# I got nothin' in this file!
-['celery']
-some_variable = floop
-mail_port = 2000
-celeryd_eta_scheduler_precision = 1.3
-celery_result_persistent = true
-celery_imports = foo.bar.baz, this.is.an.import
+[celery]
+SOME_VARIABLE = floop
+MAIL_PORT = 2000
+CELERYD_ETA_SCHEDULER_PRECISION = 1.3
+CELERY_RESULT_PERSISTENT = true
+CELERY_IMPORTS = foo.bar.baz, this.is.an.import
diff --git a/mediagoblin/tests/fake_celery_conf_mgdb.ini b/mediagoblin/tests/fake_celery_conf_mgdb.ini
deleted file mode 100644
index 52671c14..00000000
--- a/mediagoblin/tests/fake_celery_conf_mgdb.ini
+++ /dev/null
@@ -1,14 +0,0 @@
-['mediagoblin']
-db_host = mongodb.example.org
-db_port = 8080
-db_name = captain_lollerskates
-
-['something']
-or = other
-
-['celery']
-some_variable = poolf
-mail_port = 2020
-celeryd_eta_scheduler_precision = 3.1
-celery_result_persistent = false
-celery_imports = baz.bar.foo, import.is.a.this
diff --git a/mediagoblin/tests/fake_config_spec.ini b/mediagoblin/tests/fake_config_spec.ini
index 9421ce36..43f2e236 100644
--- a/mediagoblin/tests/fake_config_spec.ini
+++ b/mediagoblin/tests/fake_config_spec.ini
@@ -7,4 +7,4 @@ num_carrots = integer(default=1)
encouragement_phrase = string()
[celery]
-eat_celery_with_carrots = boolean(default=True) \ No newline at end of file
+EAT_CELERY_WITH_CARROTS = boolean(default=True) \ No newline at end of file
diff --git a/mediagoblin/tests/test_celery_setup.py b/mediagoblin/tests/test_celery_setup.py
index c9c77821..fd600f56 100644
--- a/mediagoblin/tests/test_celery_setup.py
+++ b/mediagoblin/tests/test_celery_setup.py
@@ -22,8 +22,6 @@ from mediagoblin.init.config import read_mediagoblin_config
TEST_CELERY_CONF_NOSPECIALDB = pkg_resources.resource_filename(
'mediagoblin.tests', 'fake_celery_conf.ini')
-TEST_CELERY_CONF_MGSPECIALDB = pkg_resources.resource_filename(
- 'mediagoblin.tests', 'fake_celery_conf_mgdb.ini')
def test_setup_celery_from_config():
@@ -51,35 +49,12 @@ def test_setup_celery_from_config():
assert fake_celery_module.CELERY_RESULT_PERSISTENT is True
assert fake_celery_module.CELERY_IMPORTS == [
'foo.bar.baz', 'this.is.an.import', 'mediagoblin.processing']
- assert fake_celery_module.CELERY_MONGODB_BACKEND_SETTINGS == {
- 'database': 'mediagoblin'}
- assert fake_celery_module.CELERY_RESULT_BACKEND == 'mongodb'
- assert fake_celery_module.BROKER_BACKEND == 'mongodb'
-
- _wipe_testmodule_clean(fake_celery_module)
-
- global_config, validation_result = read_mediagoblin_config(
- TEST_CELERY_CONF_MGSPECIALDB)
- app_config = global_config['mediagoblin']
-
- celery_setup.setup_celery_from_config(
- app_config, global_config,
- 'mediagoblin.tests.fake_celery_module', set_environ=False)
-
- from mediagoblin.tests import fake_celery_module
- assert fake_celery_module.SOME_VARIABLE == 'poolf'
- assert fake_celery_module.MAIL_PORT == 2020
- assert isinstance(fake_celery_module.MAIL_PORT, int)
- assert fake_celery_module.CELERYD_ETA_SCHEDULER_PRECISION == 3.1
- assert isinstance(fake_celery_module.CELERYD_ETA_SCHEDULER_PRECISION, float)
- assert fake_celery_module.CELERY_RESULT_PERSISTENT is False
- assert fake_celery_module.CELERY_IMPORTS == [
- 'baz.bar.foo', 'import.is.a.this', 'mediagoblin.processing']
- assert fake_celery_module.CELERY_MONGODB_BACKEND_SETTINGS == {
- 'database': 'captain_lollerskates',
- 'host': 'mongodb.example.org',
- 'port': 8080}
- assert fake_celery_module.CELERY_RESULT_BACKEND == 'mongodb'
- assert fake_celery_module.BROKER_BACKEND == 'mongodb'
- assert fake_celery_module.BROKER_HOST == 'mongodb.example.org'
- assert fake_celery_module.BROKER_PORT == 8080
+ assert fake_celery_module.CELERY_RESULT_BACKEND == 'database'
+ assert fake_celery_module.CELERY_RESULT_DBURI == (
+ 'sqlite:///' +
+ pkg_resources.resource_filename('mediagoblin.tests', 'celery.db'))
+
+ assert fake_celery_module.BROKER_TRANSPORT == 'sqlalchemy'
+ assert fake_celery_module.BROKER_HOST == (
+ 'sqlite:///' +
+ pkg_resources.resource_filename('mediagoblin.tests', 'kombu.db'))
diff --git a/mediagoblin/tests/test_config.py b/mediagoblin/tests/test_config.py
index c596f6a6..7d8c65c1 100644
--- a/mediagoblin/tests/test_config.py
+++ b/mediagoblin/tests/test_config.py
@@ -37,7 +37,7 @@ def test_read_mediagoblin_config():
assert this_conf['carrotapp']['carrotcake'] == False
assert this_conf['carrotapp']['num_carrots'] == 1
assert not this_conf['carrotapp'].has_key('encouragement_phrase')
- assert this_conf['celery']['eat_celery_with_carrots'] == True
+ assert this_conf['celery']['EAT_CELERY_WITH_CARROTS'] == True
# A good file
this_conf, validation_results = config.read_mediagoblin_config(
@@ -48,7 +48,7 @@ def test_read_mediagoblin_config():
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['celery']['eat_celery_with_carrots'] == False
+ assert this_conf['celery']['EAT_CELERY_WITH_CARROTS'] == False
# A bad file
this_conf, validation_results = config.read_mediagoblin_config(
@@ -61,7 +61,7 @@ def test_read_mediagoblin_config():
assert this_conf['carrotapp']['encouragement_phrase'] == \
"586956856856"
assert this_conf['carrotapp']['blah_blah'] == "blah!"
- assert this_conf['celery']['eat_celery_with_carrots'] == "pants"
+ assert this_conf['celery']['EAT_CELERY_WITH_CARROTS'] == "pants"
def test_generate_validation_report():
@@ -89,7 +89,7 @@ There were validation problems loading this config file:
expected_warnings = [
'carrotapp:carrotcake = the value "slobber" is of the wrong type.',
'carrotapp:num_carrots = the value "GROSS" is of the wrong type.',
- 'celery:eat_celery_with_carrots = the value "pants" is of the wrong type.']
+ 'celery:EAT_CELERY_WITH_CARROTS = the value "pants" is of the wrong type.']
warnings = report.splitlines()[2:]
assert len(warnings) == 3
diff --git a/mediagoblin/tests/test_mgoblin_app.ini b/mediagoblin/tests/test_mgoblin_app.ini
index c91ed92b..01bf0972 100644
--- a/mediagoblin/tests/test_mgoblin_app.ini
+++ b/mediagoblin/tests/test_mgoblin_app.ini
@@ -26,4 +26,4 @@ data_dir = %(here)s/test_user_dev/beaker/cache/data
lock_dir = %(here)s/test_user_dev/beaker/cache/lock
[celery]
-celery_always_eager = true
+CELERY_ALWAYS_EAGER = true
diff --git a/mediagoblin/tests/test_paste.ini b/mediagoblin/tests/test_paste.ini
index bd57994b..d7c18642 100644
--- a/mediagoblin/tests/test_paste.ini
+++ b/mediagoblin/tests/test_paste.ini
@@ -29,7 +29,7 @@ beaker.session.data_dir = %(here)s/test_user_dev/beaker/sessions/data
beaker.session.lock_dir = %(here)s/test_user_dev/beaker/sessions/lock
[celery]
-celery_always_eager = true
+CELERY_ALWAYS_EAGER = true
[server:main]
use = egg:Paste#http
diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py
new file mode 100644
index 00000000..507a7725
--- /dev/null
+++ b/mediagoblin/tests/test_sql_migrations.py
@@ -0,0 +1,884 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2012, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import copy
+
+from sqlalchemy import (
+ Table, Column, MetaData, Index,
+ Integer, Float, Unicode, UnicodeText, DateTime, Boolean,
+ ForeignKey, UniqueConstraint, PickleType, VARCHAR)
+from sqlalchemy.orm import sessionmaker, relationship
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.sql import select, insert
+from migrate import changeset
+
+from mediagoblin.db.sql.base import GMGTableBase
+from mediagoblin.db.sql.util import MigrationManager, RegisterMigration
+
+
+# This one will get filled with local migrations
+FULL_MIGRATIONS = {}
+
+
+#######################################################
+# Migration set 1: Define initial models, no migrations
+#######################################################
+
+Base1 = declarative_base(cls=GMGTableBase)
+
+class Creature1(Base1):
+ __tablename__ = "creature"
+
+ id = Column(Integer, primary_key=True)
+ name = Column(Unicode, unique=True, nullable=False, index=True)
+ num_legs = Column(Integer, nullable=False)
+ is_demon = Column(Boolean)
+
+class Level1(Base1):
+ __tablename__ = "level"
+
+ id = Column(Unicode, primary_key=True)
+ name = Column(Unicode)
+ description = Column(Unicode)
+ exits = Column(PickleType)
+
+SET1_MODELS = [Creature1, Level1]
+
+SET1_MIGRATIONS = {}
+
+#######################################################
+# Migration set 2: A few migrations and new model
+#######################################################
+
+Base2 = declarative_base(cls=GMGTableBase)
+
+class Creature2(Base2):
+ __tablename__ = "creature"
+
+ id = Column(Integer, primary_key=True)
+ name = Column(Unicode, unique=True, nullable=False, index=True)
+ num_legs = Column(Integer, nullable=False)
+ magical_powers = relationship("CreaturePower2")
+
+class CreaturePower2(Base2):
+ __tablename__ = "creature_power"
+
+ id = Column(Integer, primary_key=True)
+ creature = Column(
+ Integer, ForeignKey('creature.id'), nullable=False)
+ name = Column(Unicode)
+ description = Column(Unicode)
+ hitpower = Column(Integer, nullable=False)
+
+class Level2(Base2):
+ __tablename__ = "level"
+
+ id = Column(Unicode, primary_key=True)
+ name = Column(Unicode)
+ description = Column(Unicode)
+
+class LevelExit2(Base2):
+ __tablename__ = "level_exit"
+
+ id = Column(Integer, primary_key=True)
+ name = Column(Unicode)
+ from_level = Column(
+ Unicode, ForeignKey('level.id'), nullable=False)
+ to_level = Column(
+ Unicode, ForeignKey('level.id'), nullable=False)
+
+SET2_MODELS = [Creature2, CreaturePower2, Level2, LevelExit2]
+
+
+@RegisterMigration(1, FULL_MIGRATIONS)
+def creature_remove_is_demon(db_conn):
+ """
+ Remove the is_demon field from the creature model. We don't need
+ it!
+ """
+ # :( Commented out 'cuz of:
+ # http://code.google.com/p/sqlalchemy-migrate/issues/detail?id=143&thanks=143&ts=1327882242
+
+ # metadata = MetaData(bind=db_conn.bind)
+ # creature_table = Table(
+ # 'creature', metadata,
+ # autoload=True, autoload_with=db_conn.bind)
+ # creature_table.drop_column('is_demon')
+ pass
+
+
+@RegisterMigration(2, FULL_MIGRATIONS)
+def creature_powers_new_table(db_conn):
+ """
+ Add a new table for creature powers. Nothing needs to go in it
+ yet though as there wasn't anything that previously held this
+ information
+ """
+ metadata = MetaData(bind=db_conn.bind)
+
+ # We have to access the creature table so sqlalchemy can make the
+ # foreign key relationship
+ creature_table = Table(
+ 'creature', metadata,
+ autoload=True, autoload_with=db_conn.bind)
+
+ creature_powers = Table(
+ 'creature_power', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('creature',
+ Integer, ForeignKey('creature.id'), nullable=False),
+ Column('name', Unicode),
+ Column('description', Unicode),
+ Column('hitpower', Integer, nullable=False))
+ metadata.create_all(db_conn.bind)
+
+
+@RegisterMigration(3, FULL_MIGRATIONS)
+def level_exits_new_table(db_conn):
+ """
+ Make a new table for level exits and move the previously pickled
+ stuff over to here (then drop the old unneeded table)
+ """
+ # First, create the table
+ # -----------------------
+ metadata = MetaData(bind=db_conn.bind)
+
+ # Minimal representation of level table.
+ # Not auto-introspecting here because of pickle table. I'm not
+ # sure sqlalchemy can auto-introspect pickle columns.
+ levels = Table(
+ 'level', metadata,
+ Column('id', Unicode, primary_key=True),
+ Column('name', Unicode),
+ Column('description', Unicode),
+ Column('exits', PickleType))
+
+ level_exits = Table(
+ 'level_exit', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('name', Unicode),
+ Column('from_level',
+ Unicode, ForeignKey('level.id'), nullable=False),
+ Column('to_level',
+ Unicode, ForeignKey('level.id'), nullable=False))
+ metadata.create_all(db_conn.bind)
+
+ # And now, convert all the old exit pickles to new level exits
+ # ------------------------------------------------------------
+
+ # query over and insert
+ result = db_conn.execute(
+ select([levels], levels.c.exits!=None))
+
+ for level in result:
+
+ for exit_name, to_level in level['exits'].iteritems():
+ # Insert the level exit
+ db_conn.execute(
+ level_exits.insert().values(
+ name=exit_name,
+ from_level=level.id,
+ to_level=to_level))
+
+ # Finally, drop the old level exits pickle table
+ # ----------------------------------------------
+ levels.drop_column('exits')
+
+
+# A hack! At this point we freeze-fame and get just a partial list of
+# migrations
+
+SET2_MIGRATIONS = copy.copy(FULL_MIGRATIONS)
+
+#######################################################
+# Migration set 3: Final migrations
+#######################################################
+
+Base3 = declarative_base(cls=GMGTableBase)
+
+class Creature3(Base3):
+ __tablename__ = "creature"
+
+ id = Column(Integer, primary_key=True)
+ name = Column(Unicode, unique=True, nullable=False, index=True)
+ num_limbs= Column(Integer, nullable=False)
+ magical_powers = relationship("CreaturePower3")
+
+class CreaturePower3(Base3):
+ __tablename__ = "creature_power"
+
+ id = Column(Integer, primary_key=True)
+ creature = Column(
+ Integer, ForeignKey('creature.id'), nullable=False, index=True)
+ name = Column(Unicode)
+ description = Column(Unicode)
+ hitpower = Column(Float, nullable=False)
+
+class Level3(Base3):
+ __tablename__ = "level"
+
+ id = Column(Unicode, primary_key=True)
+ name = Column(Unicode)
+ description = Column(Unicode)
+
+class LevelExit3(Base3):
+ __tablename__ = "level_exit"
+
+ id = Column(Integer, primary_key=True)
+ name = Column(Unicode)
+ from_level = Column(
+ Unicode, ForeignKey('level.id'), nullable=False, index=True)
+ to_level = Column(
+ Unicode, ForeignKey('level.id'), nullable=False, index=True)
+
+
+SET3_MODELS = [Creature3, CreaturePower3, Level3, LevelExit3]
+SET3_MIGRATIONS = FULL_MIGRATIONS
+
+
+@RegisterMigration(4, FULL_MIGRATIONS)
+def creature_num_legs_to_num_limbs(db_conn):
+ """
+ Turns out we're tracking all sorts of limbs, not "legs"
+ specifically. Humans would be 4 here, for instance. So we
+ renamed the column.
+ """
+ metadata = MetaData(bind=db_conn.bind)
+ creature_table = Table(
+ 'creature', metadata,
+ autoload=True, autoload_with=db_conn.bind)
+ creature_table.c.num_legs.alter(name=u"num_limbs")
+
+
+@RegisterMigration(5, FULL_MIGRATIONS)
+def level_exit_index_from_and_to_level(db_conn):
+ """
+ Index the from and to levels of the level exit table.
+ """
+ metadata = MetaData(bind=db_conn.bind)
+ level_exit = Table(
+ 'level_exit', metadata,
+ autoload=True, autoload_with=db_conn.bind)
+ Index('ix_level_exit_from_level',
+ level_exit.c.from_level).create(db_conn.bind)
+ Index('ix_level_exit_to_level',
+ level_exit.c.to_level).create(db_conn.bind)
+
+
+@RegisterMigration(6, FULL_MIGRATIONS)
+def creature_power_index_creature(db_conn):
+ """
+ Index our foreign key relationship to the creatures
+ """
+ metadata = MetaData(bind=db_conn.bind)
+ creature_power = Table(
+ 'creature_power', metadata,
+ autoload=True, autoload_with=db_conn.bind)
+ Index('ix_creature_power_creature',
+ creature_power.c.creature).create(db_conn.bind)
+
+
+@RegisterMigration(7, FULL_MIGRATIONS)
+def creature_power_hitpower_to_float(db_conn):
+ """
+ Convert hitpower column on creature power table from integer to
+ float.
+
+ Turns out we want super precise values of how much hitpower there
+ really is.
+ """
+ metadata = MetaData(bind=db_conn.bind)
+
+ # We have to access the creature table so sqlalchemy can make the
+ # foreign key relationship
+ creature_table = Table(
+ 'creature', metadata,
+ autoload=True, autoload_with=db_conn.bind)
+
+ creature_power = Table(
+ 'creature_power', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('creature', Integer,
+ ForeignKey('creature.id'), nullable=False,
+ index=True),
+ Column('name', Unicode),
+ Column('description', Unicode),
+ Column('hitpower', Integer, nullable=False))
+
+ creature_power.c.hitpower.alter(type=Float)
+
+
+def _insert_migration1_objects(session):
+ """
+ Test objects to insert for the first set of things
+ """
+ # Insert creatures
+ session.add_all(
+ [Creature1(name=u'centipede',
+ num_legs=100,
+ is_demon=False),
+ Creature1(name=u'wolf',
+ num_legs=4,
+ is_demon=False),
+ # don't ask me what a wizardsnake is.
+ Creature1(name=u'wizardsnake',
+ num_legs=0,
+ is_demon=True)])
+
+ # Insert levels
+ session.add_all(
+ [Level1(id=u'necroplex',
+ name=u'The Necroplex',
+ description=u'A complex full of pure deathzone.',
+ exits={
+ 'deathwell': 'evilstorm',
+ 'portal': 'central_park'}),
+ Level1(id=u'evilstorm',
+ name=u'Evil Storm',
+ description=u'A storm full of pure evil.',
+ exits={}), # you can't escape the evilstorm
+ Level1(id=u'central_park',
+ name=u'Central Park, NY, NY',
+ description=u"New York's friendly Central Park.",
+ exits={
+ 'portal': 'necroplex'})])
+
+ session.commit()
+
+
+def _insert_migration2_objects(session):
+ """
+ Test objects to insert for the second set of things
+ """
+ # Insert creatures
+ session.add_all(
+ [Creature2(
+ name=u'centipede',
+ num_legs=100),
+ Creature2(
+ name=u'wolf',
+ num_legs=4,
+ magical_powers = [
+ CreaturePower2(
+ name=u"ice breath",
+ description=u"A blast of icy breath!",
+ hitpower=20),
+ CreaturePower2(
+ name=u"death stare",
+ description=u"A frightening stare, for sure!",
+ hitpower=45)]),
+ Creature2(
+ name=u'wizardsnake',
+ num_legs=0,
+ magical_powers=[
+ CreaturePower2(
+ name=u'death_rattle',
+ description=u'A rattle... of DEATH!',
+ hitpower=1000),
+ CreaturePower2(
+ name=u'sneaky_stare',
+ description=u"The sneakiest stare you've ever seen!",
+ hitpower=300),
+ CreaturePower2(
+ name=u'slithery_smoke',
+ description=u"A blast of slithery, slithery smoke.",
+ hitpower=10),
+ CreaturePower2(
+ name=u'treacherous_tremors',
+ description=u"The ground shakes beneath footed animals!",
+ hitpower=0)])])
+
+ # Insert levels
+ session.add_all(
+ [Level2(id=u'necroplex',
+ name=u'The Necroplex',
+ description=u'A complex full of pure deathzone.'),
+ Level2(id=u'evilstorm',
+ name=u'Evil Storm',
+ description=u'A storm full of pure evil.',
+ exits=[]), # you can't escape the evilstorm
+ Level2(id=u'central_park',
+ name=u'Central Park, NY, NY',
+ description=u"New York's friendly Central Park.")])
+
+ # necroplex exits
+ session.add_all(
+ [LevelExit2(name=u'deathwell',
+ from_level=u'necroplex',
+ to_level=u'evilstorm'),
+ LevelExit2(name=u'portal',
+ from_level=u'necroplex',
+ to_level=u'central_park')])
+
+ # there are no evilstorm exits because there is no exit from the
+ # evilstorm
+
+ # central park exits
+ session.add_all(
+ [LevelExit2(name=u'portal',
+ from_level=u'central_park',
+ to_level=u'necroplex')])
+
+ session.commit()
+
+
+def _insert_migration3_objects(session):
+ """
+ Test objects to insert for the third set of things
+ """
+ # Insert creatures
+ session.add_all(
+ [Creature3(
+ name=u'centipede',
+ num_limbs=100),
+ Creature3(
+ name=u'wolf',
+ num_limbs=4,
+ magical_powers = [
+ CreaturePower3(
+ name=u"ice breath",
+ description=u"A blast of icy breath!",
+ hitpower=20.0),
+ CreaturePower3(
+ name=u"death stare",
+ description=u"A frightening stare, for sure!",
+ hitpower=45.0)]),
+ Creature3(
+ name=u'wizardsnake',
+ num_limbs=0,
+ magical_powers=[
+ CreaturePower3(
+ name=u'death_rattle',
+ description=u'A rattle... of DEATH!',
+ hitpower=1000.0),
+ CreaturePower3(
+ name=u'sneaky_stare',
+ description=u"The sneakiest stare you've ever seen!",
+ hitpower=300.0),
+ CreaturePower3(
+ name=u'slithery_smoke',
+ description=u"A blast of slithery, slithery smoke.",
+ hitpower=10.0),
+ CreaturePower3(
+ name=u'treacherous_tremors',
+ description=u"The ground shakes beneath footed animals!",
+ hitpower=0.0)])],
+ # annnnnd one more to test a floating point hitpower
+ Creature3(
+ name=u'deity',
+ numb_limbs=30,
+ magical_powers=[
+ CreaturePower3(
+ name=u'smite',
+ description=u'Smitten by holy wrath!',
+ hitpower=9999.9)]))
+
+ # Insert levels
+ session.add_all(
+ [Level3(id=u'necroplex',
+ name=u'The Necroplex',
+ description=u'A complex full of pure deathzone.'),
+ Level3(id=u'evilstorm',
+ name=u'Evil Storm',
+ description=u'A storm full of pure evil.',
+ exits=[]), # you can't escape the evilstorm
+ Level3(id=u'central_park',
+ name=u'Central Park, NY, NY',
+ description=u"New York's friendly Central Park.")])
+
+ # necroplex exits
+ session.add_all(
+ [LevelExit3(name=u'deathwell',
+ from_level=u'necroplex',
+ to_level=u'evilstorm'),
+ LevelExit3(name=u'portal',
+ from_level=u'necroplex',
+ to_level=u'central_park')])
+
+ # there are no evilstorm exits because there is no exit from the
+ # evilstorm
+
+ # central park exits
+ session.add_all(
+ [LevelExit3(name=u'portal',
+ from_level=u'central_park',
+ to_level=u'necroplex')])
+
+ session.commit()
+
+
+class CollectingPrinter(object):
+ def __init__(self):
+ self.collection = []
+
+ def __call__(self, string):
+ self.collection.append(string)
+
+ @property
+ def combined_string(self):
+ return u''.join(self.collection)
+
+
+def create_test_engine():
+ from sqlalchemy import create_engine
+ engine = create_engine('sqlite:///:memory:', echo=False)
+ Session = sessionmaker(bind=engine)
+ return engine, Session
+
+
+def assert_col_type(column, this_class):
+ assert isinstance(column.type, this_class)
+
+
+def _get_level3_exits(session, level):
+ return dict(
+ [(level_exit.name, level_exit.to_level)
+ for level_exit in
+ session.query(LevelExit3).filter_by(from_level=level.id)])
+
+
+def test_set1_to_set3():
+ # Create / connect to database
+ # ----------------------------
+
+ engine, Session = create_test_engine()
+
+ # Create tables by migrating on empty initial set
+ # -----------------------------------------------
+
+ printer = CollectingPrinter()
+ migration_manager = MigrationManager(
+ '__main__', SET1_MODELS, SET1_MIGRATIONS, Session(),
+ printer)
+
+ # Check latest migration and database current migration
+ assert migration_manager.latest_migration == 0
+ assert migration_manager.database_current_migration == None
+
+ result = migration_manager.init_or_migrate()
+
+ # Make sure output was "inited"
+ assert result == u'inited'
+ # Check output
+ assert printer.combined_string == (
+ "-> Initializing main mediagoblin tables... done.\n")
+ # Check version in database
+ assert migration_manager.latest_migration == 0
+ assert migration_manager.database_current_migration == 0
+
+ # Install the initial set
+ # -----------------------
+
+ _insert_migration1_objects(Session())
+
+ # Try to "re-migrate" with same manager settings... nothing should happen
+ migration_manager = MigrationManager(
+ '__main__', SET1_MODELS, SET1_MIGRATIONS, Session(),
+ printer)
+ assert migration_manager.init_or_migrate() == None
+
+ # Check version in database
+ assert migration_manager.latest_migration == 0
+ assert migration_manager.database_current_migration == 0
+
+ # Sanity check a few things in the database...
+ metadata = MetaData(bind=engine)
+
+ # Check the structure of the creature table
+ creature_table = Table(
+ 'creature', metadata,
+ autoload=True, autoload_with=engine)
+ assert set(creature_table.c.keys()) == set(
+ ['id', 'name', 'num_legs', 'is_demon'])
+ assert_col_type(creature_table.c.id, Integer)
+ assert_col_type(creature_table.c.name, VARCHAR)
+ assert creature_table.c.name.nullable is False
+ #assert creature_table.c.name.index is True
+ #assert creature_table.c.name.unique is True
+ assert_col_type(creature_table.c.num_legs, Integer)
+ assert creature_table.c.num_legs.nullable is False
+ assert_col_type(creature_table.c.is_demon, Boolean)
+
+ # Check the structure of the level table
+ level_table = Table(
+ 'level', metadata,
+ autoload=True, autoload_with=engine)
+ assert set(level_table.c.keys()) == set(
+ ['id', 'name', 'description', 'exits'])
+ assert_col_type(level_table.c.id, VARCHAR)
+ assert level_table.c.id.primary_key is True
+ assert_col_type(level_table.c.name, VARCHAR)
+ assert_col_type(level_table.c.description, VARCHAR)
+ # Skipping exits... Not sure if we can detect pickletype, not a
+ # big deal regardless.
+
+ # Now check to see if stuff seems to be in there.
+ session = Session()
+
+ creature = session.query(Creature1).filter_by(
+ name=u'centipede').one()
+ assert creature.num_legs == 100
+ assert creature.is_demon == False
+
+ creature = session.query(Creature1).filter_by(
+ name=u'wolf').one()
+ assert creature.num_legs == 4
+ assert creature.is_demon == False
+
+ creature = session.query(Creature1).filter_by(
+ name=u'wizardsnake').one()
+ assert creature.num_legs == 0
+ assert creature.is_demon == True
+
+ level = session.query(Level1).filter_by(
+ id=u'necroplex').one()
+ assert level.name == u'The Necroplex'
+ assert level.description == u'A complex full of pure deathzone.'
+ assert level.exits == {
+ 'deathwell': 'evilstorm',
+ 'portal': 'central_park'}
+
+ level = session.query(Level1).filter_by(
+ id=u'evilstorm').one()
+ assert level.name == u'Evil Storm'
+ assert level.description == u'A storm full of pure evil.'
+ assert level.exits == {} # You still can't escape the evilstorm!
+
+ level = session.query(Level1).filter_by(
+ id=u'central_park').one()
+ assert level.name == u'Central Park, NY, NY'
+ assert level.description == u"New York's friendly Central Park."
+ assert level.exits == {
+ 'portal': 'necroplex'}
+
+ # Create new migration manager, but make sure the db migration
+ # isn't said to be updated yet
+ printer = CollectingPrinter()
+ migration_manager = MigrationManager(
+ '__main__', SET3_MODELS, SET3_MIGRATIONS, Session(),
+ printer)
+
+ assert migration_manager.latest_migration == 7
+ assert migration_manager.database_current_migration == 0
+
+ # Migrate
+ result = migration_manager.init_or_migrate()
+
+ # Make sure result was "migrated"
+ assert result == u'migrated'
+
+ # TODO: Check output to user
+ assert printer.combined_string == """\
+-> Updating main mediagoblin tables:
+ + Running migration 1, "creature_remove_is_demon"... done.
+ + Running migration 2, "creature_powers_new_table"... done.
+ + Running migration 3, "level_exits_new_table"... done.
+ + Running migration 4, "creature_num_legs_to_num_limbs"... done.
+ + Running migration 5, "level_exit_index_from_and_to_level"... done.
+ + Running migration 6, "creature_power_index_creature"... done.
+ + Running migration 7, "creature_power_hitpower_to_float"... done.
+"""
+
+ # Make sure version matches expected
+ migration_manager = MigrationManager(
+ '__main__', SET3_MODELS, SET3_MIGRATIONS, Session(),
+ printer)
+ assert migration_manager.latest_migration == 7
+ assert migration_manager.database_current_migration == 7
+
+ # Check all things in database match expected
+
+ # Check the creature table
+ metadata = MetaData(bind=engine)
+ creature_table = Table(
+ 'creature', metadata,
+ autoload=True, autoload_with=engine)
+ # assert set(creature_table.c.keys()) == set(
+ # ['id', 'name', 'num_limbs'])
+ assert set(creature_table.c.keys()) == set(
+ [u'id', 'name', u'num_limbs', u'is_demon'])
+ assert_col_type(creature_table.c.id, Integer)
+ assert_col_type(creature_table.c.name, VARCHAR)
+ assert creature_table.c.name.nullable is False
+ #assert creature_table.c.name.index is True
+ #assert creature_table.c.name.unique is True
+ assert_col_type(creature_table.c.num_limbs, Integer)
+ assert creature_table.c.num_limbs.nullable is False
+
+ # Check the CreaturePower table
+ creature_power_table = Table(
+ 'creature_power', metadata,
+ autoload=True, autoload_with=engine)
+ assert set(creature_power_table.c.keys()) == set(
+ ['id', 'creature', 'name', 'description', 'hitpower'])
+ assert_col_type(creature_power_table.c.id, Integer)
+ assert_col_type(creature_power_table.c.creature, Integer)
+ assert creature_power_table.c.creature.nullable is False
+ assert_col_type(creature_power_table.c.name, VARCHAR)
+ assert_col_type(creature_power_table.c.description, VARCHAR)
+ assert_col_type(creature_power_table.c.hitpower, Float)
+ assert creature_power_table.c.hitpower.nullable is False
+
+ # Check the structure of the level table
+ level_table = Table(
+ 'level', metadata,
+ autoload=True, autoload_with=engine)
+ assert set(level_table.c.keys()) == set(
+ ['id', 'name', 'description'])
+ assert_col_type(level_table.c.id, VARCHAR)
+ assert level_table.c.id.primary_key is True
+ assert_col_type(level_table.c.name, VARCHAR)
+ assert_col_type(level_table.c.description, VARCHAR)
+
+ # Check the structure of the level_exits table
+ level_exit_table = Table(
+ 'level_exit', metadata,
+ autoload=True, autoload_with=engine)
+ assert set(level_exit_table.c.keys()) == set(
+ ['id', 'name', 'from_level', 'to_level'])
+ assert_col_type(level_exit_table.c.id, Integer)
+ assert_col_type(level_exit_table.c.name, VARCHAR)
+ assert_col_type(level_exit_table.c.from_level, VARCHAR)
+ assert level_exit_table.c.from_level.nullable is False
+ #assert level_exit_table.c.from_level.index is True
+ assert_col_type(level_exit_table.c.to_level, VARCHAR)
+ assert level_exit_table.c.to_level.nullable is False
+ #assert level_exit_table.c.to_level.index is True
+
+ # Now check to see if stuff seems to be in there.
+ session = Session()
+ creature = session.query(Creature3).filter_by(
+ name=u'centipede').one()
+ assert creature.num_limbs == 100.0
+ assert creature.magical_powers == []
+
+ creature = session.query(Creature3).filter_by(
+ name=u'wolf').one()
+ assert creature.num_limbs == 4.0
+ assert creature.magical_powers == []
+
+ creature = session.query(Creature3).filter_by(
+ name=u'wizardsnake').one()
+ assert creature.num_limbs == 0.0
+ assert creature.magical_powers == []
+
+ level = session.query(Level3).filter_by(
+ id=u'necroplex').one()
+ assert level.name == u'The Necroplex'
+ assert level.description == u'A complex full of pure deathzone.'
+ level_exits = _get_level3_exits(session, level)
+ assert level_exits == {
+ u'deathwell': u'evilstorm',
+ u'portal': u'central_park'}
+
+ level = session.query(Level3).filter_by(
+ id=u'evilstorm').one()
+ assert level.name == u'Evil Storm'
+ assert level.description == u'A storm full of pure evil.'
+ level_exits = _get_level3_exits(session, level)
+ assert level_exits == {} # You still can't escape the evilstorm!
+
+ level = session.query(Level3).filter_by(
+ id=u'central_park').one()
+ assert level.name == u'Central Park, NY, NY'
+ assert level.description == u"New York's friendly Central Park."
+ level_exits = _get_level3_exits(session, level)
+ assert level_exits == {
+ 'portal': 'necroplex'}
+
+
+#def test_set2_to_set3():
+ # Create / connect to database
+ # Create tables by migrating on empty initial set
+
+ # Install the initial set
+ # Check version in database
+ # Sanity check a few things in the database
+
+ # Migrate
+ # Make sure version matches expected
+ # Check all things in database match expected
+ # pass
+
+
+#def test_set1_to_set2_to_set3():
+ # Create / connect to database
+ # Create tables by migrating on empty initial set
+
+ # Install the initial set
+ # Check version in database
+ # Sanity check a few things in the database
+
+ # Migrate
+ # Make sure version matches expected
+ # Check all things in database match expected
+
+ # Migrate again
+ # Make sure version matches expected again
+ # Check all things in database match expected again
+
+ ##### Set2
+ # creature_table = Table(
+ # 'creature', metadata,
+ # autoload=True, autoload_with=db_conn.bind)
+ # assert set(creature_table.c.keys()) == set(
+ # ['id', 'name', 'num_legs'])
+ # assert_col_type(creature_table.c.id, Integer)
+ # assert_col_type(creature_table.c.name, VARCHAR)
+ # assert creature_table.c.name.nullable is False
+ # assert creature_table.c.name.index is True
+ # assert creature_table.c.name.unique is True
+ # assert_col_type(creature_table.c.num_legs, Integer)
+ # assert creature_table.c.num_legs.nullable is False
+
+ # # Check the CreaturePower table
+ # creature_power_table = Table(
+ # 'creature_power', metadata,
+ # autoload=True, autoload_with=db_conn.bind)
+ # assert set(creature_power_table.c.keys()) == set(
+ # ['id', 'creature', 'name', 'description', 'hitpower'])
+ # assert_col_type(creature_power_table.c.id, Integer)
+ # assert_col_type(creature_power_table.c.creature, Integer)
+ # assert creature_power_table.c.creature.nullable is False
+ # assert_col_type(creature_power_table.c.name, VARCHAR)
+ # assert_col_type(creature_power_table.c.description, VARCHAR)
+ # assert_col_type(creature_power_table.c.hitpower, Integer)
+ # assert creature_power_table.c.hitpower.nullable is False
+
+ # # Check the structure of the level table
+ # level_table = Table(
+ # 'level', metadata,
+ # autoload=True, autoload_with=db_conn.bind)
+ # assert set(level_table.c.keys()) == set(
+ # ['id', 'name', 'description'])
+ # assert_col_type(level_table.c.id, VARCHAR)
+ # assert level_table.c.id.primary_key is True
+ # assert_col_type(level_table.c.name, VARCHAR)
+ # assert_col_type(level_table.c.description, VARCHAR)
+
+ # # Check the structure of the level_exits table
+ # level_exit_table = Table(
+ # 'level_exit', metadata,
+ # autoload=True, autoload_with=db_conn.bind)
+ # assert set(level_exit_table.c.keys()) == set(
+ # ['id', 'name', 'from_level', 'to_level'])
+ # assert_col_type(level_exit_table.c.id, Integer)
+ # assert_col_type(level_exit_table.c.name, VARCHAR)
+ # assert_col_type(level_exit_table.c.from_level, VARCHAR)
+ # assert level_exit_table.c.from_level.nullable is False
+ # assert_col_type(level_exit_table.c.to_level, VARCHAR)
+
+ # pass
diff --git a/mediagoblin/tests/test_submission.py b/mediagoblin/tests/test_submission.py
index 217926a4..2f11bdfb 100644
--- a/mediagoblin/tests/test_submission.py
+++ b/mediagoblin/tests/test_submission.py
@@ -140,7 +140,7 @@ class TestSubmission:
context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/user_pages/user.html']
request = context['request']
media = request.db.MediaEntry.find({'title': 'Balanced Goblin'})[0]
- assert_equal(media['tags'],
+ assert_equal(media.tags,
[{'name': u'yin', 'slug': u'yin'},
{'name': u'yang', 'slug': u'yang'}])
@@ -255,7 +255,7 @@ class TestSubmission:
{'title': 'Malicious Upload 2'})
assert_equal(entry.state, 'failed')
assert_equal(
- entry['fail_error'],
+ entry.fail_error,
u'mediagoblin.processing:BadMediaFail')
# Test non-supported file with .png extension
@@ -275,5 +275,5 @@ class TestSubmission:
{'title': 'Malicious Upload 3'})
assert_equal(entry.state, 'failed')
assert_equal(
- entry['fail_error'],
+ entry.fail_error,
u'mediagoblin.processing:BadMediaFail')
diff --git a/mediagoblin/tools/files.py b/mediagoblin/tools/files.py
index b2f316b2..25c1a6e6 100644
--- a/mediagoblin/tools/files.py
+++ b/mediagoblin/tools/files.py
@@ -27,6 +27,6 @@ def delete_media_files(media):
mg_globals.public_store.delete_file(
listpath)
- for attachment in media['attachment_files']:
+ for attachment in media.attachment_files:
mg_globals.public_store.delete_file(
attachment['filepath'])
diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py
index 82791278..530dea64 100644
--- a/mediagoblin/user_pages/views.py
+++ b/mediagoblin/user_pages/views.py
@@ -18,7 +18,6 @@ from webob import exc
from mediagoblin import messages, mg_globals
from mediagoblin.db.util import DESCENDING, ObjectId
-from mediagoblin.tools.text import cleaned_markdown_conversion
from mediagoblin.tools.response import render_to_response, render_404, redirect
from mediagoblin.tools.translate import pass_to_ugettext as _
from mediagoblin.tools.pagination import Pagination
@@ -143,12 +142,11 @@ def media_post_comment(request, media):
assert request.method == 'POST'
comment = request.db.MediaComment()
- comment['media_entry'] = media._id
- comment['author'] = request.user._id
- comment['content'] = unicode(request.POST['comment_content'])
- comment['content_html'] = cleaned_markdown_conversion(comment['content'])
+ comment.media_entry = media.id
+ comment.author = request.user.id
+ comment.content = unicode(request.POST['comment_content'])
- if not comment['content'].strip():
+ if not comment.content.strip():
messages.add_message(
request,
messages.ERROR,
@@ -250,7 +248,7 @@ def atom_feed(request):
for entry in cursor:
feed.add(entry.get('title'),
- entry.get('description_html'),
+ entry.description_html,
id=entry.url_for_self(request.urlgen,qualified=True),
content_type='html',
author={
diff --git a/setup.py b/setup.py
index 9dd8964a..3e382e56 100644
--- a/setup.py
+++ b/setup.py
@@ -62,6 +62,9 @@ setup(
'webtest',
'ConfigObj',
'Markdown',
+ 'sqlalchemy',
+ 'sqlalchemy-migrate',
+ 'kombu-sqlalchemy',
## For now we're expecting that users will install this from
## their package managers.
# 'lxml',