Resolve merge conflicts
This commit is contained in:
commit
aa8804719c
1
.gitignore
vendored
1
.gitignore
vendored
@ -15,6 +15,7 @@
|
||||
/user_dev/
|
||||
/paste_local.ini
|
||||
/mediagoblin_local.ini
|
||||
/mediagoblin.db
|
||||
/server-log.txt
|
||||
|
||||
# Tests
|
||||
|
2
COPYING
2
COPYING
@ -30,7 +30,7 @@ If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
JavaScript files located in the ``mediagoblin/`` directory tree
|
||||
are free software: you can redistribute and/or modify them under the
|
||||
terms of the GNU Lesser General Public License as published by 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.
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -60,7 +60,9 @@ def register(request):
|
||||
if request.method == 'POST' and register_form.validate():
|
||||
# TODO: Make sure the user doesn't exist already
|
||||
username = unicode(request.POST['username'].lower())
|
||||
email = unicode(request.POST['email'].lower())
|
||||
em_user, em_dom = unicode(request.POST['email']).split("@", 1)
|
||||
em_dom = em_dom.lower()
|
||||
email = em_user + "@" + em_dom
|
||||
users_with_username = request.db.User.find(
|
||||
{'username': username}).count()
|
||||
users_with_email = request.db.User.find(
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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,17 @@ 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')
|
||||
|
@ -23,7 +23,7 @@ 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
|
||||
|
||||
###################
|
||||
# Custom validators
|
||||
@ -59,7 +59,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 +75,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,
|
||||
}
|
||||
@ -112,9 +110,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 +174,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.
|
||||
@ -257,7 +251,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 +260,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 +270,7 @@ class MediaComment(Document):
|
||||
'author': ObjectId,
|
||||
'created': datetime.datetime,
|
||||
'content': unicode,
|
||||
'content_html': unicode}
|
||||
}
|
||||
|
||||
required_fields = [
|
||||
'media_entry', 'author', 'created', 'content']
|
||||
|
@ -56,7 +56,7 @@ def convert_users(mk_db):
|
||||
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
|
||||
|
||||
@ -77,9 +77,9 @@ def convert_media_entries(mk_db):
|
||||
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")
|
||||
|
||||
@ -133,7 +133,7 @@ def convert_media_comments(mk_db):
|
||||
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")
|
||||
|
||||
@ -148,8 +148,7 @@ def convert_media_comments(mk_db):
|
||||
def main():
|
||||
global_config, app_config = setup_global_and_app_config("mediagoblin.ini")
|
||||
|
||||
sql_conn, sql_db = sql_connect({'sql_engine': 'sqlite:///mediagoblin.db'})
|
||||
|
||||
sql_conn, sql_db = sql_connect(app_config)
|
||||
mk_conn, mk_db = mongo_connect(app_config)
|
||||
|
||||
Base.metadata.create_all(sql_db.engine)
|
||||
|
@ -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
|
||||
|
17
mediagoblin/db/sql/migrations.py
Normal file
17
mediagoblin/db/sql/migrations.py
Normal file
@ -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 = {}
|
@ -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)
|
||||
|
||||
@ -209,10 +214,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 +227,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 +236,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:'
|
||||
|
271
mediagoblin/db/sql/util.py
Normal file
271
mediagoblin/db/sql/util.py
Normal file
@ -0,0 +1,271 @@
|
||||
# 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/>.
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
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__])
|
@ -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'])
|
||||
@ -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,
|
||||
|
@ -53,6 +53,10 @@ 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'},
|
||||
}
|
||||
|
||||
|
||||
|
89
mediagoblin/gmg_commands/dbupdate.py
Normal file
89
mediagoblin/gmg_commands/dbupdate.py
Normal file
@ -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()
|
@ -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,
|
||||
|
17
mediagoblin/media_types/ascii/migrations.py
Normal file
17
mediagoblin/media_types/ascii/migrations.py
Normal file
@ -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 = {}
|
34
mediagoblin/media_types/ascii/models.py
Normal file
34
mediagoblin/media_types/ascii/models.py
Normal file
@ -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]
|
17
mediagoblin/media_types/image/migrations.py
Normal file
17
mediagoblin/media_types/image/migrations.py
Normal file
@ -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 = {}
|
19
mediagoblin/media_types/image/models.py
Normal file
19
mediagoblin/media_types/image/models.py
Normal file
@ -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]
|
17
mediagoblin/media_types/video/migrations.py
Normal file
17
mediagoblin/media_types/video/migrations.py
Normal file
@ -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 = {}
|
34
mediagoblin/media_types/video/models.py
Normal file
34
mediagoblin/media_types/video/models.py
Normal file
@ -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 VideoData(Base):
|
||||
__tablename__ = "video_data"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
media_entry = Column(
|
||||
Integer, ForeignKey('media_entries.id'), nullable=False)
|
||||
|
||||
|
||||
DATA_MODEL = VideoData
|
||||
MODELS = [VideoData]
|
@ -109,7 +109,7 @@ input, textarea {
|
||||
.container {
|
||||
margin: auto;
|
||||
width: 96%;
|
||||
max-width: 820px;
|
||||
max-width: 940px;
|
||||
}
|
||||
|
||||
header {
|
||||
@ -151,7 +151,7 @@ footer {
|
||||
}
|
||||
|
||||
.media_pane {
|
||||
width: 560px;
|
||||
width: 640px;
|
||||
margin-left: 0px;
|
||||
margin-right: 10px;
|
||||
float: left;
|
||||
@ -164,8 +164,9 @@ img.media_image {
|
||||
}
|
||||
|
||||
.media_sidebar {
|
||||
width: 240px;
|
||||
width: 280px;
|
||||
margin-left: 10px;
|
||||
margin-right: 0px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
@ -231,6 +232,15 @@ text-align: center;
|
||||
float: right;
|
||||
}
|
||||
|
||||
textarea#comment_content {
|
||||
resize: vertical;
|
||||
width: 634px;
|
||||
height: 90px;
|
||||
border: none;
|
||||
background-color: #f1f1f1;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both;
|
||||
display: block;
|
||||
@ -240,15 +250,6 @@ text-align: center;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
h3.sidedata {
|
||||
border: none;
|
||||
background-color: #212121;
|
||||
border-radius: 4px 4px 0 0;
|
||||
padding: 3px 8px;
|
||||
margin: 20px 0 5px 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* forms */
|
||||
|
||||
.form_box,.form_box_xl {
|
||||
@ -330,18 +331,13 @@ textarea#description, textarea#bio {
|
||||
}
|
||||
|
||||
textarea#comment_content {
|
||||
resize: vertical;
|
||||
width: 100%;
|
||||
width: 634px;
|
||||
height: 90px;
|
||||
border: none;
|
||||
background-color: #f1f1f1;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
#form_comment .form_field_input {
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
/* media galleries */
|
||||
|
||||
.media_thumbnail {
|
||||
@ -352,7 +348,6 @@ textarea#comment_content {
|
||||
margin: 0px 5px 10px 5px;
|
||||
text-align: center;
|
||||
font-size: 0.875em;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.media_thumbnail a {
|
||||
@ -364,12 +359,6 @@ textarea#comment_content {
|
||||
|
||||
h2.media_title {
|
||||
margin-bottom: 0px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
p.context {
|
||||
display: inline-block;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
p.media_specs {
|
||||
@ -396,21 +385,19 @@ img.media_icon {
|
||||
|
||||
/* navigation */
|
||||
|
||||
.navigation {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.navigation_button {
|
||||
width: 135px;
|
||||
display: inline-block;
|
||||
display: block;
|
||||
float: left;
|
||||
text-align: center;
|
||||
background-color: #1d1d1d;
|
||||
border: 1px solid;
|
||||
border-color: #2c2c2c #232323 #1a1a1a;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
padding: 4px 0 8px;
|
||||
margin: 0 0 6px
|
||||
padding: 12px 0 16px;
|
||||
font-size: 1.4em;
|
||||
margin: 0 0 20px
|
||||
}
|
||||
|
||||
.navigation_left {
|
||||
@ -495,18 +482,11 @@ table.media_panel th {
|
||||
}
|
||||
|
||||
/* Media queries and other responsivisivity */
|
||||
|
||||
@media screen and (max-width: 820px) {
|
||||
@media screen and (max-width: 680px) {
|
||||
.media_pane {
|
||||
width: 100%;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.media_sidebar {
|
||||
width: 100%;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
img.media_image {
|
||||
width: 100%;
|
||||
display: inline;
|
||||
@ -515,12 +495,13 @@ table.media_panel th {
|
||||
.media_thumbnail {
|
||||
width: 21%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 960px) {
|
||||
.profile_sidebar {
|
||||
width: 100%;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.profile_showcase {
|
||||
width: 100%;
|
||||
margin: 0px;
|
||||
|
@ -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
|
||||
@ -66,8 +66,6 @@ def submit_start(request):
|
||||
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
|
||||
|
||||
|
@ -40,84 +40,67 @@
|
||||
{% endblock mediagoblin_head %}
|
||||
|
||||
{% block mediagoblin_content %}
|
||||
{% trans user_url=request.urlgen(
|
||||
'mediagoblin.user_pages.user_home',
|
||||
user=media.get_uploader.username),
|
||||
username=media.get_uploader.username -%}
|
||||
<p class="context">❖ Browsing media by <a href="{{ user_url }}">{{ username }}</a></p>
|
||||
{%- endtrans %}
|
||||
{% include "mediagoblin/utils/prev_next.html" %}
|
||||
<div class="media_image_container">
|
||||
{% block mediagoblin_media %}
|
||||
{% set display_media = request.app.public_store.file_url(
|
||||
media.get_display_media(media.media_files)) %}
|
||||
{# if there's a medium file size, that means the medium size
|
||||
# isn't the original... so link to the original!
|
||||
#}
|
||||
{% if media.media_files.has_key('medium') %}
|
||||
<a href="{{ request.app.public_store.file_url(
|
||||
media.media_files['original']) }}">
|
||||
<div class="media_pane">
|
||||
<div class="media_image_container">
|
||||
{% block mediagoblin_media %}
|
||||
{% set display_media = request.app.public_store.file_url(
|
||||
media.get_display_media(media.media_files)) %}
|
||||
{# if there's a medium file size, that means the medium size
|
||||
# isn't the original... so link to the original!
|
||||
#}
|
||||
{% if media.media_files.has_key('medium') %}
|
||||
<a href="{{ request.app.public_store.file_url(
|
||||
media.media_files['original']) }}">
|
||||
<img class="media_image"
|
||||
src="{{ display_media }}"
|
||||
alt="Image for {{ media.title }}" />
|
||||
</a>
|
||||
{% else %}
|
||||
<img class="media_image"
|
||||
src="{{ display_media }}"
|
||||
alt="Image for {{ media.title }}" />
|
||||
</a>
|
||||
{% else %}
|
||||
<img class="media_image"
|
||||
src="{{ display_media }}"
|
||||
alt="Image for {{ media.title }}" />
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
<div class="media_pane">
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
<h2 class="media_title">
|
||||
{{ media.title }}
|
||||
</h2>
|
||||
{% if request.user and
|
||||
(media.uploader == request.user._id or
|
||||
request.user.is_admin) %}
|
||||
{% set edit_url = request.urlgen('mediagoblin.edit.edit_media',
|
||||
user= media.get_uploader.username,
|
||||
media= media._id) %}
|
||||
<a class="button_action" href="{{ edit_url }}">{% trans %}Edit{% endtrans %}</a>
|
||||
{% set delete_url = request.urlgen('mediagoblin.user_pages.media_confirm_delete',
|
||||
user= media.get_uploader.username,
|
||||
media= media._id) %}
|
||||
<a class="button_action" href="{{ delete_url }}">{% trans %}Delete{% endtrans %}</a>
|
||||
{% endif %}
|
||||
{% autoescape False %}
|
||||
<p>{{ media.description_html }}</p>
|
||||
{% endautoescape %}
|
||||
{% if media.attachment_files|count %}
|
||||
<h3>Attachments</h3>
|
||||
<ul>
|
||||
{% for attachment in media.attachment_files %}
|
||||
<li>
|
||||
<a href="{{ request.app.public_store.file_url(attachment.filepath) }}">
|
||||
{{ attachment.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if app_config['allow_attachments']
|
||||
and request.user
|
||||
and (media.uploader == request.user._id
|
||||
or request.user.is_admin) %}
|
||||
<p>
|
||||
<a href="{{ request.urlgen('mediagoblin.edit.attachments',
|
||||
user=media.get_uploader.username,
|
||||
media=media._id) }}">Add attachment</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
<p class="media_specs">
|
||||
{% trans date=media.created.strftime("%Y-%m-%d") -%}
|
||||
Added on {{ date }}.
|
||||
{%- endtrans %}
|
||||
{% if request.user and
|
||||
(media.uploader == request.user._id or
|
||||
request.user.is_admin) %}
|
||||
{% set edit_url = request.urlgen('mediagoblin.edit.edit_media',
|
||||
user= media.get_uploader.username,
|
||||
media= media._id) %}
|
||||
<a class="button_action" href="{{ edit_url }}">{% trans %}Edit{% endtrans %}</a>
|
||||
{% set delete_url = request.urlgen('mediagoblin.user_pages.media_confirm_delete',
|
||||
user= media.get_uploader.username,
|
||||
media= media._id) %}
|
||||
<a class="button_action" href="{{ delete_url }}">{% trans %}Delete{% endtrans %}</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% if comments %}
|
||||
<h3>
|
||||
{% if comments.count()==1 %}
|
||||
{% trans comment_count=comments.count() -%}{{ comment_count }} comment{%- endtrans %}
|
||||
{% elif comments.count()>1 %}
|
||||
{% trans comment_count=comments.count() -%}{{ comment_count }} comments{%- endtrans %}
|
||||
{% else %}
|
||||
{% trans %}No comments yet.{% endtrans %}
|
||||
{% endif %}
|
||||
<div class="right_align">
|
||||
<a
|
||||
{% if not request.user %}
|
||||
href="{{ request.urlgen('mediagoblin.auth.login') }}"
|
||||
{% endif %}
|
||||
class="button_action" id="button_addcomment" title="Add a comment">
|
||||
{% trans %}Add a comment{% endtrans %}
|
||||
{% trans %}Add one{% endtrans %}
|
||||
</a>
|
||||
</div>
|
||||
</h3>
|
||||
@ -167,11 +150,35 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="media_sidebar">
|
||||
{% trans date=media.created.strftime("%Y-%m-%d") -%}
|
||||
<h3 class="sidedata">Added on</h3>
|
||||
<p>{{ date }}</p>
|
||||
{% trans user_url=request.urlgen(
|
||||
'mediagoblin.user_pages.user_home',
|
||||
user=media.get_uploader.username),
|
||||
username=media.get_uploader.username -%}
|
||||
<p>❖ Browsing media by <a href="{{ user_url }}">{{ username }}</a></p>
|
||||
{%- endtrans %}
|
||||
|
||||
{% include "mediagoblin/utils/prev_next.html" %}
|
||||
{% if media.attachment_files|count %}
|
||||
<h3>Attachments</h3>
|
||||
<ul>
|
||||
{% for attachment in media.attachment_files %}
|
||||
<li>
|
||||
<a href="{{ request.app.public_store.file_url(attachment.filepath) }}">
|
||||
{{ attachment.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if app_config['allow_attachments']
|
||||
and request.user
|
||||
and (media.uploader == request.user._id
|
||||
or request.user.is_admin) %}
|
||||
<p>
|
||||
<a href="{{ request.urlgen('mediagoblin.edit.attachments',
|
||||
user=media.get_uploader.username,
|
||||
media=media._id) }}">Add attachment</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if media.tags %}
|
||||
{% include "mediagoblin/utils/tags.html" %}
|
||||
{% endif %}
|
||||
|
@ -20,7 +20,7 @@
|
||||
{% if media.media_data.has_key('exif')
|
||||
and app_config['exif_visible']
|
||||
and media.media_data.exif.has_key('useful') %}
|
||||
<h3 class="sidedata">EXIF</h3>
|
||||
<h4>EXIF</h4>
|
||||
<table>
|
||||
{% for key, tag in media.media_data.exif.useful.items() %}
|
||||
<tr>
|
||||
|
@ -20,7 +20,7 @@
|
||||
{% if media.media_data.has_key('gps')
|
||||
and app_config['geolocation_map_visible']
|
||||
and media.media_data.gps %}
|
||||
<h3 class="sidedata">Location</h3>
|
||||
<h4>Map</h4>
|
||||
<div>
|
||||
{% set gps = media.media_data.gps %}
|
||||
<div id="tile-map" style="width: 100%; height: 196px;">
|
||||
|
@ -17,8 +17,8 @@
|
||||
#}
|
||||
|
||||
{% block license_content -%}
|
||||
<h3 class="sidedata">{% trans %}License{% endtrans %}</h3>
|
||||
<p>
|
||||
{% trans %}License:{% endtrans %}
|
||||
{% if media.license %}
|
||||
<a href="{{ media.license }}">{{ media.get_license_data().abbreviation }}</a>
|
||||
{% else %}
|
||||
|
@ -19,23 +19,29 @@
|
||||
{% from "mediagoblin/utils/pagination.html" import render_pagination %}
|
||||
|
||||
{% macro media_grid(request, media_entries, col_number=5) %}
|
||||
<ul class="thumb_gallery">
|
||||
<table class="thumb_gallery">
|
||||
{% for row in gridify_cursor(media_entries, col_number) %}
|
||||
{% for entry in row %}
|
||||
{% set entry_url = entry.url_for_self(request.urlgen) %}
|
||||
<li class="media_thumbnail">
|
||||
<a href="{{ entry_url }}">
|
||||
<img src="{{ request.app.public_store.file_url(
|
||||
entry.media_files['thumb']) }}" />
|
||||
</a>
|
||||
{% if entry.title %}
|
||||
<br />
|
||||
<a href="{{ entry_url }}">{{ entry.title }}</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
<tr class="thumb_row
|
||||
{%- if loop.first %} thumb_row_first
|
||||
{%- elif loop.last %} thumb_row_last{% endif %}">
|
||||
{% for entry in row %}
|
||||
{% set entry_url = entry.url_for_self(request.urlgen) %}
|
||||
<td class="media_thumbnail thumb_entry
|
||||
{%- if loop.first %} thumb_entry_first
|
||||
{%- elif loop.last %} thumb_entry_last{% endif %}">
|
||||
<a href="{{ entry_url }}">
|
||||
<img src="{{ request.app.public_store.file_url(
|
||||
entry.media_files['thumb']) }}" />
|
||||
</a>
|
||||
{% if entry.title %}
|
||||
<br />
|
||||
<a href="{{ entry_url }}">{{ entry.title }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</table>
|
||||
{%- endmacro %}
|
||||
|
||||
{#
|
||||
|
@ -21,28 +21,26 @@
|
||||
{% set next_entry_url = media.url_to_next(request.urlgen) %}
|
||||
|
||||
{% if prev_entry_url or next_entry_url %}
|
||||
<div class="navigation">
|
||||
{# There are no previous entries for the very first media entry #}
|
||||
{% if prev_entry_url %}
|
||||
<a class="navigation_button navigation_left" href="{{ prev_entry_url }}">
|
||||
← {% trans %}newer{% endtrans %}
|
||||
</a>
|
||||
{% else %}
|
||||
{# This is the first entry. display greyed-out 'previous' image #}
|
||||
<p class="navigation_button navigation_left">
|
||||
← {% trans %}newer{% endtrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{# Likewise, this could be the very last media entry #}
|
||||
{% if next_entry_url %}
|
||||
<a class="navigation_button" href="{{ next_entry_url }}">
|
||||
{% trans %}older{% endtrans %} →
|
||||
</a>
|
||||
{% else %}
|
||||
{# This is the last entry. display greyed-out 'next' image #}
|
||||
<p class="navigation_button">
|
||||
{% trans %}older{% endtrans %} →
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{# There are no previous entries for the very first media entry #}
|
||||
{% if prev_entry_url %}
|
||||
<a class="navigation_button navigation_left" href="{{ prev_entry_url }}">
|
||||
← {% trans %}newer{% endtrans %}
|
||||
</a>
|
||||
{% else %}
|
||||
{# This is the first entry. display greyed-out 'previous' image #}
|
||||
<p class="navigation_button navigation_left">
|
||||
← {% trans %}newer{% endtrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{# Likewise, this could be the very last media entry #}
|
||||
{% if next_entry_url %}
|
||||
<a class="navigation_button" href="{{ next_entry_url }}">
|
||||
{% trans %}older{% endtrans %} →
|
||||
</a>
|
||||
{% else %}
|
||||
{# This is the last entry. display greyed-out 'next' image #}
|
||||
<p class="navigation_button">
|
||||
{% trans %}older{% endtrans %} →
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
@ -17,17 +17,16 @@
|
||||
#}
|
||||
|
||||
{% block tags_content -%}
|
||||
<h3 class="sidedata">Tagged with</h3>
|
||||
<p>
|
||||
<p>{% trans %}View more media tagged with{% endtrans %}
|
||||
{% for tag in media.tags %}
|
||||
{% if loop.last %}
|
||||
{# the 'and' should only appear if there is more than one tag #}
|
||||
{% if media.tags|length > 1 %}
|
||||
·
|
||||
{% trans %}or{% endtrans %}
|
||||
{% endif %}
|
||||
<a href="{{ request.urlgen(
|
||||
'mediagoblin.listings.tags_listing',
|
||||
tag=tag['slug']) }}">{{ tag['name'] }}</a>
|
||||
tag=tag['slug']) }}">{{ tag['name'] }}</a>.
|
||||
{% elif loop.revindex == 2 %}
|
||||
<a href="{{ request.urlgen(
|
||||
'mediagoblin.listings.tags_listing',
|
||||
@ -35,7 +34,7 @@
|
||||
{% else %}
|
||||
<a href="{{ request.urlgen(
|
||||
'mediagoblin.listings.tags_listing',
|
||||
tag=tag['slug']) }}">{{ tag['name'] }}</a> ·
|
||||
tag=tag['slug']) }}">{{ tag['name'] }}</a>,
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
|
884
mediagoblin/tests/test_sql_migrations.py
Normal file
884
mediagoblin/tests/test_sql_migrations.py
Normal file
@ -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
|
@ -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
|
||||
@ -146,7 +145,6 @@ def media_post_comment(request, media):
|
||||
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'])
|
||||
|
||||
if not comment['content'].strip():
|
||||
messages.add_message(
|
||||
@ -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={
|
||||
|
Loading…
x
Reference in New Issue
Block a user