Merge branch 'master' into test_submission_views_365
This commit is contained in:
commit
4ce57ac85d
@ -40,10 +40,6 @@ def setup_celery_from_config(app_config, global_config,
|
|||||||
- set_environ: if set, this will CELERY_CONFIG_MODULE to the
|
- set_environ: if set, this will CELERY_CONFIG_MODULE to the
|
||||||
settings_module
|
settings_module
|
||||||
"""
|
"""
|
||||||
if app_config.get('celery_setup_elsewhere') == True:
|
|
||||||
# Don't setup celery based on our config file.
|
|
||||||
return
|
|
||||||
|
|
||||||
if global_config.has_key('celery'):
|
if global_config.has_key('celery'):
|
||||||
celery_conf = global_config['celery']
|
celery_conf = global_config['celery']
|
||||||
else:
|
else:
|
||||||
|
@ -23,7 +23,8 @@ from mediagoblin.celery_setup import setup_celery_from_config
|
|||||||
OUR_MODULENAME = __name__
|
OUR_MODULENAME = __name__
|
||||||
|
|
||||||
|
|
||||||
def setup_self(check_environ_for_conf=True, module_name=OUR_MODULENAME):
|
def setup_self(check_environ_for_conf=True, module_name=OUR_MODULENAME,
|
||||||
|
default_conf_file='mediagoblin.ini'):
|
||||||
"""
|
"""
|
||||||
Transform this module into a celery config module by reading the
|
Transform this module into a celery config module by reading the
|
||||||
mediagoblin config file. Set the environment variable
|
mediagoblin config file. Set the environment variable
|
||||||
@ -36,9 +37,9 @@ def setup_self(check_environ_for_conf=True, module_name=OUR_MODULENAME):
|
|||||||
"""
|
"""
|
||||||
if check_environ_for_conf:
|
if check_environ_for_conf:
|
||||||
mgoblin_conf_file = os.path.abspath(
|
mgoblin_conf_file = os.path.abspath(
|
||||||
os.environ.get('MEDIAGOBLIN_CONFIG', 'mediagoblin.ini'))
|
os.environ.get('MEDIAGOBLIN_CONFIG', default_conf_file))
|
||||||
else:
|
else:
|
||||||
mgoblin_conf_file = 'mediagoblin.ini'
|
mgoblin_conf_file = default_conf_file
|
||||||
|
|
||||||
if not os.path.exists(mgoblin_conf_file):
|
if not os.path.exists(mgoblin_conf_file):
|
||||||
raise IOError(
|
raise IOError(
|
||||||
@ -48,6 +49,7 @@ def setup_self(check_environ_for_conf=True, module_name=OUR_MODULENAME):
|
|||||||
# this is the module that gets set up.
|
# this is the module that gets set up.
|
||||||
os.environ['CELERY_CONFIG_MODULE'] = module_name
|
os.environ['CELERY_CONFIG_MODULE'] = module_name
|
||||||
app.MediaGoblinApp(mgoblin_conf_file, setup_celery=False)
|
app.MediaGoblinApp(mgoblin_conf_file, setup_celery=False)
|
||||||
|
|
||||||
setup_celery_from_config(
|
setup_celery_from_config(
|
||||||
mg_globals.app_config, mg_globals.global_config,
|
mg_globals.app_config, mg_globals.global_config,
|
||||||
settings_module=module_name,
|
settings_module=module_name,
|
||||||
|
@ -16,11 +16,15 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from mediagoblin.tests.tools import TEST_APP_CONFIG
|
||||||
from mediagoblin.celery_setup.from_celery import setup_self
|
from mediagoblin.celery_setup.from_celery import setup_self
|
||||||
|
|
||||||
|
|
||||||
OUR_MODULENAME = __name__
|
OUR_MODULENAME = __name__
|
||||||
|
CELERY_SETUP = False
|
||||||
|
|
||||||
|
|
||||||
if os.environ.get('CELERY_CONFIG_MODULE') == OUR_MODULENAME:
|
if os.environ.get('CELERY_CONFIG_MODULE') == OUR_MODULENAME:
|
||||||
setup_self(check_environ_for_conf=False, module_name=OUR_MODULENAME)
|
setup_self(check_environ_for_conf=False, module_name=OUR_MODULENAME,
|
||||||
|
default_conf_file=TEST_APP_CONFIG)
|
||||||
|
CELERY_SETUP = True
|
||||||
|
118
mediagoblin/db/indexes.py
Normal file
118
mediagoblin/db/indexes.py
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
# GNU MediaGoblin -- federated, autonomous media hosting
|
||||||
|
# Copyright (C) 2011 Free Software Foundation, Inc
|
||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Indexes for the local database.
|
||||||
|
|
||||||
|
To add new indexes
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Indexes are recorded in the following format:
|
||||||
|
|
||||||
|
ACTIVE_INDEXES = {
|
||||||
|
'collection_name': {
|
||||||
|
'identifier': { # key identifier used for possibly deprecating later
|
||||||
|
'index': [index_foo_goes_here]}}
|
||||||
|
|
||||||
|
... and anything else being parameters to the create_index function
|
||||||
|
(including unique=True, etc)
|
||||||
|
|
||||||
|
Current indexes must be registered in ACTIVE_INDEXES... deprecated
|
||||||
|
indexes should be marked in DEPRECATED_INDEXES.
|
||||||
|
|
||||||
|
Remember, ordering of compound indexes MATTERS. Read below for more.
|
||||||
|
|
||||||
|
REQUIRED READING:
|
||||||
|
- http://kylebanker.com/blog/2010/09/21/the-joy-of-mongodb-indexes/
|
||||||
|
|
||||||
|
- http://www.mongodb.org/display/DOCS/Indexes
|
||||||
|
- http://www.mongodb.org/display/DOCS/Indexing+Advice+and+FAQ
|
||||||
|
|
||||||
|
|
||||||
|
To remove deprecated indexes
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Removing deprecated indexes is easier, just do:
|
||||||
|
|
||||||
|
INACTIVE_INDEXES = {
|
||||||
|
'collection_name': [
|
||||||
|
'deprecated_index_identifier1', 'deprecated_index_identifier2']}
|
||||||
|
|
||||||
|
... etc.
|
||||||
|
|
||||||
|
If an index has been deprecated that identifier should NEVER BE USED
|
||||||
|
AGAIN. Eg, if you previously had 'awesomepants_unique', you shouldn't
|
||||||
|
use 'awesomepants_unique' again, you should create a totally new name
|
||||||
|
or at worst use 'awesomepants_unique2'.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pymongo import ASCENDING, DESCENDING
|
||||||
|
|
||||||
|
|
||||||
|
################
|
||||||
|
# Active indexes
|
||||||
|
################
|
||||||
|
ACTIVE_INDEXES = {}
|
||||||
|
|
||||||
|
# MediaEntry indexes
|
||||||
|
# ------------------
|
||||||
|
|
||||||
|
MEDIAENTRY_INDEXES = {
|
||||||
|
'uploader_slug_unique': {
|
||||||
|
# Matching an object to an uploader + slug.
|
||||||
|
# MediaEntries are unique on these two combined, eg:
|
||||||
|
# /u/${myuser}/m/${myslugname}/
|
||||||
|
'index': [('uploader', ASCENDING),
|
||||||
|
('slug', ASCENDING)],
|
||||||
|
'unique': True},
|
||||||
|
|
||||||
|
'created': {
|
||||||
|
# A global index for all media entries created, in descending
|
||||||
|
# order. This is used for the site's frontpage.
|
||||||
|
'index': [('created', DESCENDING)]},
|
||||||
|
|
||||||
|
'uploader_created': {
|
||||||
|
# Indexing on uploaders and when media entries are created.
|
||||||
|
# Used for showing a user gallery, etc.
|
||||||
|
'index': [('uploader', ASCENDING),
|
||||||
|
('created', DESCENDING)]}}
|
||||||
|
|
||||||
|
|
||||||
|
ACTIVE_INDEXES['media_entries'] = MEDIAENTRY_INDEXES
|
||||||
|
|
||||||
|
|
||||||
|
# User indexes
|
||||||
|
# ------------
|
||||||
|
|
||||||
|
USER_INDEXES = {
|
||||||
|
'username_unique': {
|
||||||
|
# Index usernames, and make sure they're unique.
|
||||||
|
# ... I guess we might need to adjust this once we're federated :)
|
||||||
|
'index': 'username',
|
||||||
|
'unique': True},
|
||||||
|
'created': {
|
||||||
|
# All most recently created users
|
||||||
|
'index': 'created'}}
|
||||||
|
|
||||||
|
|
||||||
|
ACTIVE_INDEXES['users'] = USER_INDEXES
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# Deprecated indexes
|
||||||
|
####################
|
||||||
|
|
||||||
|
DEPRECATED_INDEXES = {}
|
@ -108,11 +108,6 @@ class MediaEntry(Document):
|
|||||||
|
|
||||||
migration_handler = migrations.MediaEntryMigration
|
migration_handler = migrations.MediaEntryMigration
|
||||||
|
|
||||||
indexes = [
|
|
||||||
# Referene uniqueness of slugs by uploader
|
|
||||||
{'fields': ['uploader', 'slug'],
|
|
||||||
'unique': True}]
|
|
||||||
|
|
||||||
def main_mediafile(self):
|
def main_mediafile(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -14,8 +14,88 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Utilities for database operations.
|
||||||
|
|
||||||
|
Some note on migration and indexing tools:
|
||||||
|
|
||||||
|
We store information about what the state of the database is in the
|
||||||
|
'mediagoblin' document of the 'app_metadata' collection. Keys in that
|
||||||
|
document relevant to here:
|
||||||
|
|
||||||
|
- 'migration_number': The integer representing the current state of
|
||||||
|
the migrations
|
||||||
|
"""
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
# Imports that other modules might use
|
# Imports that other modules might use
|
||||||
from pymongo import DESCENDING
|
from pymongo import DESCENDING
|
||||||
from pymongo.errors import InvalidId
|
from pymongo.errors import InvalidId
|
||||||
from mongokit import ObjectId
|
from mongokit import ObjectId
|
||||||
|
|
||||||
|
from mediagoblin.db.indexes import ACTIVE_INDEXES, DEPRECATED_INDEXES
|
||||||
|
|
||||||
|
|
||||||
|
def add_new_indexes(database, active_indexes=ACTIVE_INDEXES):
|
||||||
|
"""
|
||||||
|
Add any new indexes to the database.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- database: pymongo or mongokit database instance.
|
||||||
|
- active_indexes: indexes to possibly add in the pattern of:
|
||||||
|
{'collection_name': {
|
||||||
|
'identifier': {
|
||||||
|
'index': [index_foo_goes_here],
|
||||||
|
'unique': True}}
|
||||||
|
where 'index' is the index to add and all other options are
|
||||||
|
arguments for collection.create_index.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A list of indexes added in form ('collection', 'index_name')
|
||||||
|
"""
|
||||||
|
indexes_added = []
|
||||||
|
|
||||||
|
for collection_name, indexes in active_indexes.iteritems():
|
||||||
|
collection = database[collection_name]
|
||||||
|
collection_indexes = collection.index_information().keys()
|
||||||
|
|
||||||
|
for index_name, index_data in indexes.iteritems():
|
||||||
|
if not index_name in collection_indexes:
|
||||||
|
# Get a copy actually so we don't modify the actual
|
||||||
|
# structure
|
||||||
|
index_data = copy.copy(index_data)
|
||||||
|
index = index_data.pop('index')
|
||||||
|
collection.create_index(
|
||||||
|
index, name=index_name, **index_data)
|
||||||
|
|
||||||
|
indexes_added.append((collection_name, index_name))
|
||||||
|
|
||||||
|
return indexes_added
|
||||||
|
|
||||||
|
|
||||||
|
def remove_deprecated_indexes(database, deprecated_indexes=DEPRECATED_INDEXES):
|
||||||
|
"""
|
||||||
|
Remove any deprecated indexes from the database.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- database: pymongo or mongokit database instance.
|
||||||
|
- deprecated_indexes: the indexes to deprecate in the pattern of:
|
||||||
|
{'collection': ['index_identifier1', 'index_identifier2']}
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A list of indexes removed in form ('collection', 'index_name')
|
||||||
|
"""
|
||||||
|
indexes_removed = []
|
||||||
|
|
||||||
|
for collection_name, index_names in deprecated_indexes.iteritems():
|
||||||
|
collection = database[collection_name]
|
||||||
|
collection_indexes = collection.index_information().keys()
|
||||||
|
|
||||||
|
for index_name in index_names:
|
||||||
|
if index_name in collection_indexes:
|
||||||
|
collection.drop_index(index_name)
|
||||||
|
|
||||||
|
indexes_removed.append((collection_name, index_name))
|
||||||
|
|
||||||
|
return indexes_removed
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
|
|
||||||
from mediagoblin.db import migrations
|
from mediagoblin.db import migrations
|
||||||
|
from mediagoblin.db import util as db_util
|
||||||
from mediagoblin.gmg_commands import util as commands_util
|
from mediagoblin.gmg_commands import util as commands_util
|
||||||
|
|
||||||
|
|
||||||
@ -27,8 +28,17 @@ def migrate_parser_setup(subparser):
|
|||||||
|
|
||||||
def migrate(args):
|
def migrate(args):
|
||||||
mgoblin_app = commands_util.setup_app(args)
|
mgoblin_app = commands_util.setup_app(args)
|
||||||
print "Applying migrations..."
|
|
||||||
|
|
||||||
|
# Clear old indexes
|
||||||
|
print "== Clearing old indexes... =="
|
||||||
|
removed_indexes = db_util.remove_deprecated_indexes(mgoblin_app.db)
|
||||||
|
|
||||||
|
for collection, index_name in removed_indexes:
|
||||||
|
print "Removed index '%s' in collection '%s'" % (
|
||||||
|
index_name, collection)
|
||||||
|
|
||||||
|
# Migrate
|
||||||
|
print "== Applying migrations... =="
|
||||||
for model_name in migrations.MIGRATE_CLASSES:
|
for model_name in migrations.MIGRATE_CLASSES:
|
||||||
model = getattr(mgoblin_app.db, model_name)
|
model = getattr(mgoblin_app.db, model_name)
|
||||||
|
|
||||||
@ -38,4 +48,10 @@ def migrate(args):
|
|||||||
migration = model.migration_handler(model)
|
migration = model.migration_handler(model)
|
||||||
migration.migrate_all(collection=model.collection)
|
migration.migrate_all(collection=model.collection)
|
||||||
|
|
||||||
print "... done."
|
# Add new indexes
|
||||||
|
print "== Adding new indexes... =="
|
||||||
|
new_indexes = db_util.add_new_indexes(mgoblin_app.db)
|
||||||
|
|
||||||
|
for collection, index_name in new_indexes:
|
||||||
|
print "Added index '%s' to collection '%s'" % (
|
||||||
|
index_name, collection)
|
||||||
|
34
mediagoblin/messages.py
Normal file
34
mediagoblin/messages.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# GNU MediaGoblin -- federated, autonomous media hosting
|
||||||
|
# Copyright (C) 2011 Free Software Foundation, Inc
|
||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
|
||||||
|
DEBUG = 'debug'
|
||||||
|
INFO = 'info'
|
||||||
|
SUCCESS = 'success'
|
||||||
|
WARNING = 'warning'
|
||||||
|
ERROR = 'error'
|
||||||
|
|
||||||
|
def add_message(request, level, text):
|
||||||
|
messages = request.session.setdefault('messages', [])
|
||||||
|
messages.append({'level': level, 'text': text})
|
||||||
|
request.session.save()
|
||||||
|
|
||||||
|
def fetch_messages(request, clear_from_session=True):
|
||||||
|
messages = request.session.get('messages')
|
||||||
|
if messages and clear_from_session:
|
||||||
|
# Save that we removed the messages from the session
|
||||||
|
request.session['messages'] = []
|
||||||
|
request.session.save()
|
||||||
|
return messages
|
@ -4,6 +4,12 @@ body {
|
|||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
padding:none;
|
padding:none;
|
||||||
margin:0px;
|
margin:0px;
|
||||||
|
height:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
margin:0px;
|
||||||
|
padding:0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Carter One font */
|
/* Carter One font */
|
||||||
@ -38,6 +44,11 @@ label {
|
|||||||
|
|
||||||
/* website structure */
|
/* website structure */
|
||||||
|
|
||||||
|
.mediagoblin_body {
|
||||||
|
position:relative;
|
||||||
|
min-height:100%;
|
||||||
|
}
|
||||||
|
|
||||||
.mediagoblin_header {
|
.mediagoblin_header {
|
||||||
width:100%;
|
width:100%;
|
||||||
height:36px;
|
height:36px;
|
||||||
@ -46,6 +57,63 @@ label {
|
|||||||
margin-bottom:40px;
|
margin-bottom:40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mediagoblin_footer {
|
||||||
|
width:100%;
|
||||||
|
height:26px;
|
||||||
|
background-color:#393939;
|
||||||
|
bottom:0px;
|
||||||
|
padding-top:8px;
|
||||||
|
position:absolute;
|
||||||
|
text-align:center;
|
||||||
|
font-size:14px;
|
||||||
|
color:#999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mediagoblin_content {
|
||||||
|
padding-bottom:74px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.mediagoblin_messages {
|
||||||
|
list-style:none inside;
|
||||||
|
color:#393932;
|
||||||
|
margin:2px;
|
||||||
|
padding:2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.mediagoblin_messages li {
|
||||||
|
background-color:#d4d4d4;
|
||||||
|
border-style:solid;
|
||||||
|
border-width:3px;
|
||||||
|
border-color:#959595;
|
||||||
|
margin:5px;
|
||||||
|
padding:8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.mediagoblin_messages li.message_success {
|
||||||
|
background-color: #88d486;
|
||||||
|
border-color: #5bba59;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.mediagoblin_messages li.message_warning {
|
||||||
|
background-color: #d4c686;
|
||||||
|
border-color: #baa959;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.mediagoblin_messages li.message_error {
|
||||||
|
background-color: #d48686;
|
||||||
|
border-color: #ba5959;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.mediagoblin_messages li.message_info {
|
||||||
|
background-color: #86b9d4;
|
||||||
|
border-color: #5998ba;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.mediagoblin_messages li.message_debug {
|
||||||
|
background-color: #aa86d4;
|
||||||
|
border-color: #8659ba;
|
||||||
|
}
|
||||||
|
|
||||||
a.mediagoblin_logo {
|
a.mediagoblin_logo {
|
||||||
width:34px;
|
width:34px;
|
||||||
height:25px;
|
height:25px;
|
||||||
@ -119,8 +187,8 @@ a.mediagoblin_logo:hover {
|
|||||||
font-size:28px;
|
font-size:28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form_field_input input {
|
.form_field_input input, .form_field_input textarea {
|
||||||
width:300px;
|
width:100%;
|
||||||
font-size:18px;
|
font-size:18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,11 +227,12 @@ ul.media_thumbnail {
|
|||||||
|
|
||||||
li.media_thumbnail {
|
li.media_thumbnail {
|
||||||
width:200px;
|
width:200px;
|
||||||
height:133px;
|
height:200px;
|
||||||
display:-moz-inline-stack;
|
display:-moz-inline-stack;
|
||||||
display:inline-block;
|
display:inline-block;
|
||||||
vertical-align:top;
|
vertical-align:top;
|
||||||
margin:0px 10px 10px 0px;
|
margin:0px 10px 10px 0px;
|
||||||
|
text-align:center;
|
||||||
zoom:1;
|
zoom:1;
|
||||||
. *display:inline;
|
. *display:inline;
|
||||||
}
|
}
|
||||||
|
@ -28,34 +28,49 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
{% block mediagoblin_body %}
|
{% block mediagoblin_body %}
|
||||||
|
<div class="mediagoblin_body">
|
||||||
{% block mediagoblin_header %}
|
{% block mediagoblin_header %}
|
||||||
<div class="mediagoblin_header">
|
<div class="mediagoblin_header">
|
||||||
<div class="container_12">
|
<div class="container_12">
|
||||||
<div class="grid_12">
|
<div class="grid_12">
|
||||||
{% block mediagoblin_logo %}
|
{% block mediagoblin_logo %}
|
||||||
<a class="mediagoblin_logo" href="{{ request.urlgen('index') }}"></a>
|
<a class="mediagoblin_logo" href="{{ request.urlgen('index') }}"></a>
|
||||||
{% endblock %}{% block mediagoblin_header_title %}{% endblock %}
|
{% endblock %}{% block mediagoblin_header_title %}{% endblock %}
|
||||||
<div class="mediagoblin_header_right">
|
<div class="mediagoblin_header_right">
|
||||||
{% if request.user %}
|
{% if request.user %}
|
||||||
<a href="{{ request.urlgen('mediagoblin.user_pages.user_home',
|
<a href="{{ request.urlgen('mediagoblin.user_pages.user_home',
|
||||||
user= request.user['username']) }}">
|
user= request.user['username']) }}">
|
||||||
{{ request.user['username'] }}</a>'s account
|
{{ request.user['username'] }}</a>'s account
|
||||||
(<a href="{{ request.urlgen('mediagoblin.auth.logout') }}">logout</a>)
|
(<a href="{{ request.urlgen('mediagoblin.auth.logout') }}">logout</a>)
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{{ request.urlgen('mediagoblin.auth.login') }}">
|
<a href="{{ request.urlgen('mediagoblin.auth.login') }}">
|
||||||
Login</a>
|
Login</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
<div class="container_12">
|
|
||||||
|
{% include "mediagoblin/utils/messages.html" %}
|
||||||
|
|
||||||
|
<div class="container_12 mediagoblin_content">
|
||||||
<div class="grid_12">
|
<div class="grid_12">
|
||||||
{% block mediagoblin_content %}
|
{% block mediagoblin_content %}
|
||||||
{% endblock mediagoblin_content %}
|
{% endblock mediagoblin_content %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% block mediagoblin_footer %}
|
||||||
|
<div class="mediagoblin_footer">
|
||||||
|
<div class="container_12">
|
||||||
|
<div class="grid_12">
|
||||||
|
Powered by <a href="http://mediagoblin.org">MediaGoblin</a>, a <a href="http://gnu.org/">GNU project</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
{% endblock mediagoblin_body %}
|
{% endblock mediagoblin_body %}
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -27,13 +27,15 @@
|
|||||||
method="POST" enctype="multipart/form-data">
|
method="POST" enctype="multipart/form-data">
|
||||||
<div class="grid_6 prefix_1 suffix_1 edit_box form_box">
|
<div class="grid_6 prefix_1 suffix_1 edit_box form_box">
|
||||||
<h1>Editing {{ media.title }}</h1>
|
<h1>Editing {{ media.title }}</h1>
|
||||||
|
<div style="text-align: center;" >
|
||||||
|
<img src="{{ request.app.public_store.file_url(
|
||||||
|
media['media_files']['thumb']) }}" />
|
||||||
|
</div>
|
||||||
{{ wtforms_util.render_divs(form) }}
|
{{ wtforms_util.render_divs(form) }}
|
||||||
<div class="form_submit_buttons">
|
<div class="form_submit_buttons">
|
||||||
<a href="{{ media.url_for_self(request.urlgen) }}">Cancel</a>
|
<a href="{{ media.url_for_self(request.urlgen) }}">Cancel</a>
|
||||||
<input type="submit" value="Save changes" class="button" />
|
<input type="submit" value="Save changes" class="button" />
|
||||||
</div>
|
</div>
|
||||||
<img src="{{ request.app.public_store.file_url(
|
|
||||||
media['media_files']['thumb']) }}" />
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@ -25,7 +25,9 @@
|
|||||||
method="POST" enctype="multipart/form-data">
|
method="POST" enctype="multipart/form-data">
|
||||||
<div class="grid_6 prefix_1 suffix_1 form_box">
|
<div class="grid_6 prefix_1 suffix_1 form_box">
|
||||||
<h1>Submit yer media</h1>
|
<h1>Submit yer media</h1>
|
||||||
{{ wtforms_util.render_divs(submit_form) }}
|
{{ wtforms_util.render_field_div(submit_form.title) }}
|
||||||
|
{{ wtforms_util.render_textarea_div(submit_form.description) }}
|
||||||
|
{{ wtforms_util.render_field_div(submit_form.file) }}
|
||||||
<div class="form_submit_buttons">
|
<div class="form_submit_buttons">
|
||||||
<input type="submit" value="Submit" class="button" />
|
<input type="submit" value="Submit" class="button" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,15 +20,16 @@
|
|||||||
{% block mediagoblin_head %}
|
{% block mediagoblin_head %}
|
||||||
<link rel="alternate" type="application/atom+xml"
|
<link rel="alternate" type="application/atom+xml"
|
||||||
href="{{ request.urlgen(
|
href="{{ request.urlgen(
|
||||||
'mediagoblin.user_pages.atom_feed',
|
'mediagoblin.user_pages.atom_feed',
|
||||||
user=user.username) }}">
|
user=user.username) }}">
|
||||||
{% endblock mediagoblin_head %}
|
{% endblock mediagoblin_head %}
|
||||||
|
|
||||||
{% block mediagoblin_content -%}
|
{% block mediagoblin_content -%}
|
||||||
{% if user %}
|
{% if user %}
|
||||||
<h1><a href="{{ request.urlgen(
|
<h1>
|
||||||
'mediagoblin.user_pages.user_home',
|
<a href="{{ request.urlgen(
|
||||||
user=user.username) }}">{{ user.username }}</a>'s media</h1>
|
'mediagoblin.user_pages.user_home',
|
||||||
|
user=user.username) }}">{{ user.username }}</a>'s media</h1>
|
||||||
|
|
||||||
{% include "mediagoblin/utils/object_gallery.html" %}
|
{% include "mediagoblin/utils/object_gallery.html" %}
|
||||||
|
|
||||||
|
@ -25,16 +25,18 @@
|
|||||||
</h1>
|
</h1>
|
||||||
<img class="media_image" src="{{ request.app.public_store.file_url(
|
<img class="media_image" src="{{ request.app.public_store.file_url(
|
||||||
media.media_files.main) }}" />
|
media.media_files.main) }}" />
|
||||||
|
<p>
|
||||||
|
Uploaded on
|
||||||
|
{{ "%4d-%02d-%02d"|format(media.created.year,
|
||||||
|
media.created.month, media.created.day) }}
|
||||||
|
by
|
||||||
|
<a href="{{ request.urlgen('mediagoblin.user_pages.user_home',
|
||||||
|
user= media.uploader().username) }}">
|
||||||
|
{{- media.uploader().username }}</a>
|
||||||
|
</p>
|
||||||
{% autoescape False %}
|
{% autoescape False %}
|
||||||
<p>{{ media.description_html }}</p>
|
<p>{{ media.description_html }}</p>
|
||||||
{% endautoescape %}
|
{% endautoescape %}
|
||||||
<p>Uploaded on
|
|
||||||
{{ "%4d-%02d-%02d"|format(media.created.year,
|
|
||||||
media.created.month, media.created.day) }}
|
|
||||||
by
|
|
||||||
<a href="{{ request.urlgen('mediagoblin.user_pages.user_home',
|
|
||||||
user= media.uploader().username) }}">
|
|
||||||
{{- media.uploader().username }}</a></p>
|
|
||||||
{% if media['uploader'] == request.user['_id'] %}
|
{% if media['uploader'] == request.user['_id'] %}
|
||||||
<p><a href="{{ request.urlgen('mediagoblin.edit.edit_media',
|
<p><a href="{{ request.urlgen('mediagoblin.edit.edit_media',
|
||||||
user= media.uploader().username,
|
user= media.uploader().username,
|
||||||
|
32
mediagoblin/templates/mediagoblin/utils/messages.html
Normal file
32
mediagoblin/templates/mediagoblin/utils/messages.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{#
|
||||||
|
# GNU MediaGoblin -- federated, autonomous media hosting
|
||||||
|
# Copyright (C) 2011 Free Software Foundation, Inc
|
||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
#}
|
||||||
|
|
||||||
|
{# Display any queued messages #}
|
||||||
|
{% set messages = fetch_messages(request) %}
|
||||||
|
{% if messages %}
|
||||||
|
<div class="container_12 mediagoblin_messages">
|
||||||
|
<div class="grid_12">
|
||||||
|
<ul class="mediagoblin_messages">
|
||||||
|
{% for msg in messages %}
|
||||||
|
<li class="message_{{ msg.level }}">{{ msg.text }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
@ -16,23 +16,47 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#}
|
#}
|
||||||
|
|
||||||
|
{# Generically render a field #}
|
||||||
|
{% macro render_field_div(field) %}
|
||||||
|
<div class="form_field_box">
|
||||||
|
<div class="form_field_label">{{ field.label }}</div>
|
||||||
|
{% if field.description -%}
|
||||||
|
<div class="form_field_description">{{ field.description }}</div>
|
||||||
|
{%- endif %}
|
||||||
|
<div class="form_field_input">{{ field }}</div>
|
||||||
|
{%- if field.errors -%}
|
||||||
|
{% for error in field.errors %}
|
||||||
|
<div class="form_field_error">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{%- endif %}
|
||||||
|
</div>
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{# Generically render a textarea
|
||||||
|
# ... mostly the same thing except it includes rows and cols #}
|
||||||
|
{% macro render_textarea_div(field, rows=8, cols=20) %}
|
||||||
|
<div class="form_field_box">
|
||||||
|
<div class="form_field_label">{{ field.label }}</div>
|
||||||
|
{% if field.description -%}
|
||||||
|
<div class="form_field_description">{{ field.description }}</div>
|
||||||
|
{%- endif %}
|
||||||
|
<div class="form_field_input">{{ field(rows=rows, cols=cols) }}</div>
|
||||||
|
{%- if field.errors -%}
|
||||||
|
{% for error in field.errors %}
|
||||||
|
<div class="form_field_error">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{%- endif %}
|
||||||
|
</div>
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
{# Auto-render a form as a series of divs #}
|
{# Auto-render a form as a series of divs #}
|
||||||
{% macro render_divs(form) -%}
|
{% macro render_divs(form) -%}
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
<div class="form_field_box">
|
{{ render_field_div(field) }}
|
||||||
<div class="form_field_label">{{ field.label }}</div>
|
|
||||||
{% if field.description -%}
|
|
||||||
<div class="form_field_description">{{ field.description }}</div>
|
|
||||||
{%- endif %}
|
|
||||||
<div class="form_field_input">{{ field }}</div>
|
|
||||||
{%- if field.errors -%}
|
|
||||||
{% for error in field.errors %}
|
|
||||||
<div class="form_field_error">
|
|
||||||
{{ error }}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{%- endif %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
|
44
mediagoblin/tests/test_messages.py
Normal file
44
mediagoblin/tests/test_messages.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# GNU MediaGoblin -- federated, autonomous media hosting
|
||||||
|
# Copyright (C) 2011 Free Software Foundation, Inc
|
||||||
|
#
|
||||||
|
# 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.messages import fetch_messages, add_message
|
||||||
|
from mediagoblin.tests.tools import setup_fresh_app
|
||||||
|
from mediagoblin import util
|
||||||
|
|
||||||
|
|
||||||
|
@setup_fresh_app
|
||||||
|
def test_messages(test_app):
|
||||||
|
"""
|
||||||
|
Added messages should show up in the request.session,
|
||||||
|
fetched messages should be the same as the added ones,
|
||||||
|
and fetching should clear the message list.
|
||||||
|
"""
|
||||||
|
# Aquire a request object
|
||||||
|
test_app.get('/')
|
||||||
|
context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
|
||||||
|
request = context['request']
|
||||||
|
|
||||||
|
# The message queue should be empty
|
||||||
|
assert request.session.get('messages', []) == []
|
||||||
|
|
||||||
|
# Adding a message should modify the session accordingly
|
||||||
|
add_message(request, 'herp_derp', 'First!')
|
||||||
|
test_msg_queue = [{'text': 'First!', 'level': 'herp_derp'}]
|
||||||
|
assert request.session['messages'] == test_msg_queue
|
||||||
|
|
||||||
|
# fetch_messages should return and empty the queue
|
||||||
|
assert fetch_messages(request) == test_msg_queue
|
||||||
|
assert request.session.get('messages') == []
|
@ -7,6 +7,9 @@ email_sender_address = "notice@mediagoblin.example.org"
|
|||||||
email_debug_mode = true
|
email_debug_mode = true
|
||||||
db_name = __mediagoblin_tests__
|
db_name = __mediagoblin_tests__
|
||||||
|
|
||||||
# Celery shouldn't be set up by the paste app factory as it's set up
|
# Celery shouldn't be set up by the application as it's setup via
|
||||||
# elsewhere
|
# mediagoblin.celery_setup.from_celery
|
||||||
celery_setup_elsewhere = true
|
celery_setup_elsewhere = true
|
||||||
|
|
||||||
|
[celery]
|
||||||
|
celery_always_eager = true
|
||||||
|
@ -21,9 +21,8 @@ import os, shutil
|
|||||||
from paste.deploy import loadapp
|
from paste.deploy import loadapp
|
||||||
from webtest import TestApp
|
from webtest import TestApp
|
||||||
|
|
||||||
from mediagoblin import util, mg_globals
|
from mediagoblin import util
|
||||||
from mediagoblin.config import read_mediagoblin_config
|
from mediagoblin.config import read_mediagoblin_config
|
||||||
from mediagoblin.celery_setup import setup_celery_from_config
|
|
||||||
from mediagoblin.decorators import _make_safe
|
from mediagoblin.decorators import _make_safe
|
||||||
from mediagoblin.db.open import setup_connection_and_db_from_config
|
from mediagoblin.db.open import setup_connection_and_db_from_config
|
||||||
|
|
||||||
@ -36,7 +35,6 @@ TEST_APP_CONFIG = pkg_resources.resource_filename(
|
|||||||
TEST_USER_DEV = pkg_resources.resource_filename(
|
TEST_USER_DEV = pkg_resources.resource_filename(
|
||||||
'mediagoblin.tests', 'test_user_dev')
|
'mediagoblin.tests', 'test_user_dev')
|
||||||
MGOBLIN_APP = None
|
MGOBLIN_APP = None
|
||||||
CELERY_SETUP = False
|
|
||||||
|
|
||||||
USER_DEV_DIRECTORIES_TO_SETUP = [
|
USER_DEV_DIRECTORIES_TO_SETUP = [
|
||||||
'media/public', 'media/queue',
|
'media/public', 'media/queue',
|
||||||
@ -60,8 +58,10 @@ def suicide_if_bad_celery_environ():
|
|||||||
def get_test_app(dump_old_app=True):
|
def get_test_app(dump_old_app=True):
|
||||||
suicide_if_bad_celery_environ()
|
suicide_if_bad_celery_environ()
|
||||||
|
|
||||||
|
# Leave this imported as it sets up celery.
|
||||||
|
from mediagoblin.celery_setup import from_tests
|
||||||
|
|
||||||
global MGOBLIN_APP
|
global MGOBLIN_APP
|
||||||
global CELERY_SETUP
|
|
||||||
|
|
||||||
# Just return the old app if that exists and it's okay to set up
|
# Just return the old app if that exists and it's okay to set up
|
||||||
# and return
|
# and return
|
||||||
@ -103,13 +103,6 @@ def get_test_app(dump_old_app=True):
|
|||||||
app = TestApp(test_app)
|
app = TestApp(test_app)
|
||||||
MGOBLIN_APP = app
|
MGOBLIN_APP = app
|
||||||
|
|
||||||
# setup celery
|
|
||||||
if not CELERY_SETUP:
|
|
||||||
setup_celery_from_config(
|
|
||||||
mg_globals.app_config, mg_globals.global_config,
|
|
||||||
set_environ=True)
|
|
||||||
CELERY_SETUP = True
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ from lxml.html.clean import Cleaner
|
|||||||
import markdown
|
import markdown
|
||||||
|
|
||||||
from mediagoblin import mg_globals
|
from mediagoblin import mg_globals
|
||||||
|
from mediagoblin import messages
|
||||||
from mediagoblin.db.util import ObjectId
|
from mediagoblin.db.util import ObjectId
|
||||||
|
|
||||||
TESTS_ENABLED = False
|
TESTS_ENABLED = False
|
||||||
@ -104,6 +105,10 @@ def get_jinja_env(template_loader, locale):
|
|||||||
mg_globals.translations.gettext,
|
mg_globals.translations.gettext,
|
||||||
mg_globals.translations.ngettext)
|
mg_globals.translations.ngettext)
|
||||||
|
|
||||||
|
# All templates will know how to ...
|
||||||
|
# ... fetch all waiting messages and remove them from the queue
|
||||||
|
template_env.globals['fetch_messages'] = messages.fetch_messages
|
||||||
|
|
||||||
if exists(locale):
|
if exists(locale):
|
||||||
SETUP_JINJA_ENVS[locale] = template_env
|
SETUP_JINJA_ENVS[locale] = template_env
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user