Merge branch 'ticket-679' into OPW-Moderation-Update

Conflicts:
	mediagoblin/auth/tools.py
	mediagoblin/auth/views.py
	mediagoblin/db/migration_tools.py
	mediagoblin/db/migrations.py
	mediagoblin/db/models.py
	mediagoblin/decorators.py
	mediagoblin/user_pages/views.py
This commit is contained in:
tilly-Q 2013-07-29 18:40:19 -04:00
commit 52a355b275
140 changed files with 6692 additions and 6036 deletions

15
AUTHORS
View File

@ -8,10 +8,12 @@ variety of different ways and this software wouldn't exist without them.
Thank you! Thank you!
* Aaron Williamson * Aaron Williamson
* Aditi Mittal
* Aeva Ntsc * Aeva Ntsc
* Alejandro Villanueva * Alejandro Villanueva
* Aleksandar Micovic * Aleksandar Micovic
* Aleksej Serdjukov * Aleksej Serdjukov
* Alon Levy
* Alex Camelio * Alex Camelio
* András Veres-Szentkirályi * András Veres-Szentkirályi
* Bassam Kurdali * Bassam Kurdali
@ -21,13 +23,17 @@ Thank you!
* Corey Farwell * Corey Farwell
* Chris Moylan * Chris Moylan
* Christopher Allan Webber * Christopher Allan Webber
* David Thompson
* Daniel Neel * Daniel Neel
* Deb Nicholson * Deb Nicholson
* Derek Moore * Derek Moore
* Duncan Paterson * Duncan Paterson
* Elrond of Samba TNG * Elrond of Samba TNG
* Emily O'Leary * Emily O'Leary
* Gabi Thume
* Gabriel Saldana
* Greg Grossmeier * Greg Grossmeier
* Hans Lo
* Jakob Kramer * Jakob Kramer
* Jef van Schendel * Jef van Schendel
* Jessica Tallon * Jessica Tallon
@ -36,25 +42,34 @@ Thank you!
* Jorge Araya Navarro * Jorge Araya Navarro
* Karen Rustad * Karen Rustad
* Kuno Woudt * Kuno Woudt
* Laura Arjona
* Larisa Hoffenbecker * Larisa Hoffenbecker
* Luke Slater * Luke Slater
* Manuel Urbano Santos * Manuel Urbano Santos
* Mark Holmquist * Mark Holmquist
* Mats Sjöberg
* Matt Lee * Matt Lee
* Michele Azzolari * Michele Azzolari
* Mike Linksvayer
* Natalie Foust-Pilcher
* Nathan Yergler * Nathan Yergler
* Odin Hørthe Omdal * Odin Hørthe Omdal
* Osama Khalid * Osama Khalid
* Pablo J. Urbano Santos * Pablo J. Urbano Santos
* Praveen Kumar
* Rasmus Larsson * Rasmus Larsson
* Rodney Ewing
* Runar Petursson * Runar Petursson
* Sacha De'Angeli * Sacha De'Angeli
* Sam Kleinman * Sam Kleinman
* Sam Tuke
* Sebastian Spaeth * Sebastian Spaeth
* Shawn Khan * Shawn Khan
* Simon Fondrie-Teitler
* Stefano Zacchiroli * Stefano Zacchiroli
* Tiberiu C. Turbureanu * Tiberiu C. Turbureanu
* Tran Thanh Bao * Tran Thanh Bao
* Tryggvi Björgvinsson
* Shawn Khan * Shawn Khan
* Will Kahn-Greene * Will Kahn-Greene

14
README
View File

@ -5,21 +5,21 @@
What is GNU MediaGoblin? What is GNU MediaGoblin?
======================== ========================
* Initially, a place to store all your photos thats as awesome as, if * A place to store all your different media (photos, videos, audios,
not more awesome than, existing network services (Flickr, SmugMug, and more!) thats as awesome as, if not more awesome than, existing
Picasa, etc) network services (Flickr, YouTube, etc)
* Customizable! * Customizable!
* A place for people to collaborate and show off original and derived * A place for people to collaborate and show off original and derived
creations. Free, as in freedom. Were a GNU project after all. creations. Free, as in freedom. Were a GNU project after all.
* Later, a place for all sorts of media, such as video, music, etc hosting. * Extensible: Plugins allow you to add new media types (3d models?
* Later, federated with OStatus! Presentations and documents? Yes, and more!) or extend old ones.
* A real community, and we'd love to have you join us!
Is it ready for me to use? Is it ready for me to use?
========================== ==========================
Yes! But with caveats. The software is usable and there are instances Yes!
running, but it's still in its early stages.
Can I help/hang out/participate/whisper sweet nothings in your ear? Can I help/hang out/participate/whisper sweet nothings in your ear?

View File

@ -0,0 +1,62 @@
.. MediaGoblin Documentation
Written in 2011, 2012 by MediaGoblin contributors
To the extent possible under law, the author(s) have dedicated all
copyright and related and neighboring rights to this software to
the public domain worldwide. This software is distributed without
any warranty.
You should have received a copy of the CC0 Public Domain
Dedication along with this software. If not, see
<http://creativecommons.org/publicdomain/zero/1.0/>.
==========
Migrations
==========
So, about migrations. Every time we change the way the database
structure works, we need to add a migration so that people running
older codebases can have their databases updated to the new structure
when they run `./bin/gmg dbupdate`.
The first time `./bin/gmg dbupdate` is run by a user, it creates the
tables at the current state that they're defined in models.py and sets
the migration number to the current migration... after all, migrations
only exist to get things to the current state of the db. After that,
every migration is run with dbupdate.
There's a few things you need to know:
- We use `sqlalchemy-migrate
<http://code.google.com/p/sqlalchemy-migrate/>`_.
See `their docs <https://sqlalchemy-migrate.readthedocs.org/>`_.
- `Alembic <https://bitbucket.org/zzzeek/alembic>`_ might be a better
choice than sqlalchemy-migrate now or in the future, but we
originally decided not to use it because it didn't have sqlite
support. It's not clear if that's changed.
- SQLAlchemy has two parts to it, the ORM and the "core" interface.
We DO NOT use the ORM when running migrations. Think about it: the
ORM is set up with an expectation that the models already reflect a
certain pattern. But if a person is moving from their old patern
and are running tools to *get to* the current pattern, of course
their current database structure doesn't match the state of the ORM!
- How to write migrations? Maybe there will be a tutorial here in the
future... in the meanwhile, look at existing migrations in
`mediagoblin/db/migrations.py` and look in
`mediagoblin/tests/test_sql_migrations.py` for examples.
- Common pattern: use `inspect_table` to get the current state
of the table before we run alterations on it.
- Make sure you set the RegisterMigration to be the next migration in
order.
- What happens if you're adding a *totally new* table? In this case,
you should copy the table in entirety as it exists into
migrations.py then create the tables based off of that... see
add_collection_tables. This is easier than reproducing the SQL by
hand.
- If you're writing a feature branch, you don't need to keep adding
migrations every time you change things around if your database
structure is in flux. Just alter your migrations so that they're
correct for the merge into master.
That's it for now! Good luck!

View File

@ -74,6 +74,7 @@ This guide covers writing new GNU MediaGoblin plugins.
pluginwriter/database pluginwriter/database
pluginwriter/api pluginwriter/api
pluginwriter/tests pluginwriter/tests
pluginwriter/media_type_hooks
Part 4: Developer's Zone Part 4: Developer's Zone
@ -87,6 +88,7 @@ This chapter contains various information for developers.
devel/codebase devel/codebase
devel/storage devel/storage
devel/originaldesigndecisions devel/originaldesigndecisions
devel/migrations
Indices and tables Indices and tables

View File

@ -69,6 +69,32 @@ example might look like::
This means that when people enable your plugin in their config you'll This means that when people enable your plugin in their config you'll
be able to provide defaults as well as type validation. be able to provide defaults as well as type validation.
You can access this via the app_config variables in mg_globals, or you
can use a shortcut to get your plugin's config section::
>>> from mediagoblin.tools import pluginapi
# Replace with the path to your plugin.
# (If an external package, it won't be part of mediagoblin.plugins)
>>> floobie_config = pluginapi.get_config('mediagoblin.plugins.floobifier')
>>> floobie_dir = floobie_config['floobie_dir']
# This is the same as the above
>>> from mediagoblin import mg_globals
>>> config = mg_globals.global_config['plugins']['mediagoblin.plugins.floobifier']
>>> floobie_dir = floobie_config['floobie_dir']
A tip: you have access to the `%(here)s` variable in your config,
which is the directory that the user's mediagoblin config is running
out of. So for example, your plugin may need a "floobie" directory to
store floobs in. You could give them a reasonable default that makes
use of the default `user_dev` location, but allow users to override
it, like so::
[plugin_spec]
floobie_dir = string(default="%(here)s/user_dev/floobs/")
Note, this is relative to the user's mediagoblin config directory,
*not* your plugin directory!
Context Hooks Context Hooks
------------- -------------

View File

@ -0,0 +1,38 @@
==================
Media Type hooks
==================
This documents the hooks that are currently available for ``media_type`` plugins.
What hooks are available?
=========================
'sniff_handler'
---------------
This hook is used by ``sniff_media`` in ``mediagoblin.media_types.__init__``.
Your media type should return its ``sniff_media`` method when this hook is
called.
.. Note::
Your ``sniff_media`` method should return either the ``media_type`` or
``None``.
'get_media_type_and_manager'
----------------------------
This hook is used by ``get_media_type_and_manager`` in
``mediagoblin.media_types.__init__``. When this hook is called, your media type
plugin should check if it can handle the given extension. If so, your media
type plugin should return the media type and media manager.
('media_manager', MEDIA_TYPE)
-----------------------------
If you already know the string representing the media type of a type
of media, you can pull down the manager specifically. Note that this
hook is not a string but a tuple of two strings, the latter being the
name of the media type.
This is used by media entries to pull down their media managers, and
so on.

View File

@ -157,13 +157,14 @@ directory. Modify these commands to reflect your own environment::
mkdir -p /srv/mediagoblin.example.org/ mkdir -p /srv/mediagoblin.example.org/
cd /srv/mediagoblin.example.org/ cd /srv/mediagoblin.example.org/
Clone the MediaGoblin repository:: Clone the MediaGoblin repository and set up the git submodules::
git clone git://gitorious.org/mediagoblin/mediagoblin.git git clone git://gitorious.org/mediagoblin/mediagoblin.git
cd mediagoblin
git submodule init && git submodule update
And set up the in-package virtualenv:: And set up the in-package virtualenv::
cd mediagoblin
(virtualenv --system-site-packages . || virtualenv .) && ./bin/python setup.py develop (virtualenv --system-site-packages . || virtualenv .) && ./bin/python setup.py develop
.. note:: .. note::
@ -194,7 +195,7 @@ This concludes the initial configuration of the development
environment. In the future, when you update your environment. In the future, when you update your
codebase, you should also run:: codebase, you should also run::
./bin/python setup.py develop --upgrade && ./bin/gmg dbupdate ./bin/python setup.py develop --upgrade && ./bin/gmg dbupdate && git submodule fetch
Note: If you are running an active site, depending on your server Note: If you are running an active site, depending on your server
configuration, you may need to stop it first or the dbupdate command configuration, you may need to stop it first or the dbupdate command

View File

@ -18,16 +18,18 @@ Media Types
==================== ====================
In the future, there will be all sorts of media types you can enable, In the future, there will be all sorts of media types you can enable,
but in the meanwhile there are three additional media types: video, audio but in the meanwhile there are five additional media types: video, audio,
and ascii art. ascii art, STL/3d models, PDF and Document.
First, you should probably read ":doc:`configuration`" to make sure First, you should probably read ":doc:`configuration`" to make sure
you know how to modify the mediagoblin config file. you know how to modify the mediagoblin config file.
Enabling Media Types Enabling Media Types
==================== ====================
.. note::
Media types are now plugins
Media types are enabled in your mediagoblin configuration file, typically it is Media types are enabled in your mediagoblin configuration file, typically it is
created by copying ``mediagoblin.ini`` to ``mediagoblin_local.ini`` and then created by copying ``mediagoblin.ini`` to ``mediagoblin_local.ini`` and then
applying your changes to ``mediagoblin_local.ini``. If you don't already have a applying your changes to ``mediagoblin_local.ini``. If you don't already have a
@ -37,11 +39,13 @@ Most media types have additional dependencies that you will have to install.
You will find descriptions on how to satisfy the requirements of each media type You will find descriptions on how to satisfy the requirements of each media type
on this page. on this page.
To enable a media type, edit the ``media_types`` list in your To enable a media type, add the the media type under the ``[plugins]`` section
``mediagoblin_local.ini``. For example, if your system supported image and in you ``mediagoblin_local.ini``. For example, if your system supported image
video media types, then the list would look like this:: and video media types, then it would look like this::
media_types = mediagoblin.media_types.image, mediagoblin.media_types.video [plugins]
[[mediagoblin.media_types.image]]
[[mediagoblin.media_types.video]]
Note that after enabling new media types, you must run dbupdate like so:: Note that after enabling new media types, you must run dbupdate like so::
@ -83,8 +87,8 @@ good/bad/ugly). On Debianoid systems
gstreamer0.10-ffmpeg gstreamer0.10-ffmpeg
Add ``mediagoblin.media_types.video`` to the ``media_types`` list in your Add ``[[mediagoblin.media_types.video]]`` under the ``[plugins]`` section in
``mediagoblin_local.ini`` and restart MediaGoblin. your ``mediagoblin_local.ini`` and restart MediaGoblin.
Run Run
@ -133,7 +137,7 @@ Then install ``scikits.audiolab`` for the spectrograms::
./bin/pip install scikits.audiolab ./bin/pip install scikits.audiolab
Add ``mediagoblin.media_types.audio`` to the ``media_types`` list in your Add ``[[mediagoblin.media_types.audio]]`` under the ``[plugins]`` section in your
``mediagoblin_local.ini`` and restart MediaGoblin. ``mediagoblin_local.ini`` and restart MediaGoblin.
Run Run
@ -158,13 +162,8 @@ library, which is necessary for creating thumbnails of ascii art
Next, modify (and possibly copy over from ``mediagoblin.ini``) your Next, modify (and possibly copy over from ``mediagoblin.ini``) your
``mediagoblin_local.ini``. In the ``[mediagoblin]`` section, add ``mediagoblin_local.ini``. In the ``[plugins]`` section, add
``mediagoblin.media_types.ascii`` to the ``media_types`` list. ``[[mediagoblin.media_types.ascii]]``.
For example, if your system supported image and ascii art media types, then
the list would look like this::
media_types = mediagoblin.media_types.image, mediagoblin.media_types.ascii
Run Run
@ -184,7 +183,7 @@ your execution path. This feature has been tested with Blender 2.63.
It may work on some earlier versions, but that is not guaranteed (and It may work on some earlier versions, but that is not guaranteed (and
is surely not to work prior to Blender 2.5X). is surely not to work prior to Blender 2.5X).
Add ``mediagoblin.media_types.stl`` to the ``media_types`` list in your Add ``[[mediagoblin.media_types.stl]]`` under the ``[plugins]`` section in your
``mediagoblin_local.ini`` and restart MediaGoblin. ``mediagoblin_local.ini`` and restart MediaGoblin.
Run Run
@ -199,8 +198,16 @@ will be able to present them to your wide audience of admirers!
PDF and Document PDF and Document
================ ================
To enable the "PDF and Document" support plugin, you need pdftocairo, pdfinfo, To enable the "PDF and Document" support plugin, you need:
unoconv with headless support. All executables must be on your execution path.
1. pdftocairo and pdfinfo for pdf only support.
2. unoconv with headless support to support converting libreoffice supported
documents as well, such as doc/ppt/xls/odf/odg/odp and more.
For the full list see mediagoblin/media_types/pdf/processing.py,
unoconv_supported.
All executables must be on your execution path.
To install this on Fedora: To install this on Fedora:
@ -208,6 +215,9 @@ To install this on Fedora:
sudo yum install -y poppler-utils unoconv libreoffice-headless sudo yum install -y poppler-utils unoconv libreoffice-headless
Note: You can leave out unoconv and libreoffice-headless if you want only pdf
support. This will result in a much smaller list of dependencies.
pdf.js relies on git submodules, so be sure you have fetched them: pdf.js relies on git submodules, so be sure you have fetched them:
.. code-block:: bash .. code-block:: bash
@ -222,7 +232,7 @@ This feature has been tested on Fedora with:
It may work on some earlier versions, but that is not guaranteed. It may work on some earlier versions, but that is not guaranteed.
Add ``mediagoblin.media_types.pdf`` to the ``media_types`` list in your Add ``[[mediagoblin.media_types.pdf]]`` under the ``[plugins]`` section in your
``mediagoblin_local.ini`` and restart MediaGoblin. ``mediagoblin_local.ini`` and restart MediaGoblin.
Run Run

View File

@ -21,11 +21,28 @@ This chapter has important information for releases in it.
If you're upgrading from a previous release, please read it If you're upgrading from a previous release, please read it
carefully, or at least skim over it. carefully, or at least skim over it.
0.4.1
=====
This is a bugfix release for 0.4.0. This only implements one major
fix in the newly released document support which prevented the
"conversion via libreoffice" feature.
If you were running 0.4.0 you can upgrade to v0.4.1 via a simple
switch and restarting mediagoblin/celery with no other actions.
Otherwise, follow 0.4.0 instructions.
0.4.0 0.4.0
===== =====
**Do this to upgrade** **Do this to upgrade**
1. Make sure to run ``bin/gmg dbupdate`` after upgrading.
1. Make sure to run
``./bin/python setup.py develop --upgrade && ./bin/gmg dbupdate``
after upgrading.
2. See "For Theme authors" if you have a custom theme. 2. See "For Theme authors" if you have a custom theme.
3. Note that ``./bin/gmg theme assetlink`` is now just 3. Note that ``./bin/gmg theme assetlink`` is now just
``./bin/gmg assetlink`` and covers both plugins and assets. ``./bin/gmg assetlink`` and covers both plugins and assets.
@ -39,6 +56,16 @@ carefully, or at least skim over it.
alias /srv/mediagoblin.example.org/mediagoblin/user_dev/plugin_static/; alias /srv/mediagoblin.example.org/mediagoblin/user_dev/plugin_static/;
} }
Similarly, if you've got a modified paste config, you may want to
borrow the app:plugin_static section from the default paste.ini
file.
5. We now use itsdangerous for sessions; if you had any references to
beaker in your paste config you can remove them. Again, see the
default paste.ini config
6. We also now use git submodules. Please do:
``git submodule init && git submodule update``
You will need to do this to use the new PDF support.
**For theme authors** **For theme authors**
If you have your own theme or you have any "user modified templates", If you have your own theme or you have any "user modified templates",
@ -51,7 +78,23 @@ please note the following:
You can easily customize this to give a welcome page appropriate to You can easily customize this to give a welcome page appropriate to
your site. your site.
**New features** **New features**
* PDF media type!
* Improved plugin system. More flexible, better documented, with a
new plugin authoring section of the docs.
* itsdangerous based sessions. No more beaker!
* New, experimental Piwigo-based API. This means you should be able
to use MediaGoblin with something like Shotwell. (Again, a word of
caution: this is *very experimental*!)
* Human readable timestamps, and the option to display the original
date of an image when available (available as the
"original_date_visible" variable)
* Moved unit testing system from nosetests to py.test so we can better
handle issues with sqlalchemy exploding with different database
configurations. Long story :)
* You can now disable the ability to post comments.
* Tags now can be up to length 255 characters by default.
0.3.3 0.3.3

View File

@ -11,19 +11,15 @@ email_sender_address = "notice@mediagoblin.example.org"
## Uncomment and change to your DB's appropiate setting. ## Uncomment and change to your DB's appropiate setting.
## Default is a local sqlite db "mediagoblin.db". ## Default is a local sqlite db "mediagoblin.db".
## Don't forget to run `./bin/gmg dbupdate` after having changed it.
# sql_engine = postgresql:///mediagoblin # sql_engine = postgresql:///mediagoblin
# set to false to enable sending notices # Set to false to enable sending notices
email_debug_mode = true email_debug_mode = true
# Set to false to disable registrations # Set to false to disable registrations
allow_registration = true allow_registration = true
## Uncomment this to turn on video or enable other media types
## You may have to install dependencies, and will have to run ./bin/gmg dbupdate
## See http://docs.mediagoblin.org/siteadmin/media-types.html for details.
# media_types = mediagoblin.media_types.image, mediagoblin.media_types.video
## Uncomment this to put some user-overriding templates here ## Uncomment this to put some user-overriding templates here
# local_templates = %(here)s/user_dev/templates/ # local_templates = %(here)s/user_dev/templates/
@ -43,7 +39,9 @@ base_url = /mgoblin_media/
[celery] [celery]
# Put celery stuff here # Put celery stuff here
# place plugins here---each in their own subsection of [plugins]. see # Place plugins here, each in their own subsection of [plugins].
# documentation for details. # See http://docs.mediagoblin.org/siteadmin/plugins.html for details.
[plugins] [plugins]
[[mediagoblin.plugins.geolocation]] [[mediagoblin.plugins.geolocation]]
[[mediagoblin.plugins.basic_auth]]
[[mediagoblin.media_types.image]]

View File

@ -23,4 +23,4 @@
# see http://www.python.org/dev/peps/pep-0386/ # see http://www.python.org/dev/peps/pep-0386/
__version__ = "0.4.0.dev" __version__ = "0.5.0.dev"

View File

@ -29,6 +29,7 @@ from mediagoblin.tools import common, session, translate, template
from mediagoblin.tools.response import render_http_exception from mediagoblin.tools.response import render_http_exception
from mediagoblin.tools.theme import register_themes from mediagoblin.tools.theme import register_themes
from mediagoblin.tools import request as mg_request from mediagoblin.tools import request as mg_request
from mediagoblin.media_types.tools import media_type_warning
from mediagoblin.mg_globals import setup_globals from mediagoblin.mg_globals import setup_globals
from mediagoblin.init.celery import setup_celery_from_config from mediagoblin.init.celery import setup_celery_from_config
from mediagoblin.init.plugins import setup_plugins from mediagoblin.init.plugins import setup_plugins
@ -37,6 +38,8 @@ from mediagoblin.init import (get_jinja_loader, get_staticdirector,
setup_storage) setup_storage)
from mediagoblin.tools.pluginapi import PluginManager, hook_transform from mediagoblin.tools.pluginapi import PluginManager, hook_transform
from mediagoblin.tools.crypto import setup_crypto from mediagoblin.tools.crypto import setup_crypto
from mediagoblin.auth.tools import check_auth_enabled, no_auth_logout
from mediagoblin import notifications
_log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
@ -67,6 +70,8 @@ class MediaGoblinApp(object):
# Open and setup the config # Open and setup the config
global_config, app_config = setup_global_and_app_config(config_path) global_config, app_config = setup_global_and_app_config(config_path)
media_type_warning()
setup_crypto() setup_crypto()
########################################## ##########################################
@ -85,7 +90,7 @@ class MediaGoblinApp(object):
setup_plugins() setup_plugins()
# Set up the database # Set up the database
self.db = setup_database() self.db = setup_database(app_config['run_migrations'])
# Register themes # Register themes
self.theme_registry, self.current_theme = register_themes(app_config) self.theme_registry, self.current_theme = register_themes(app_config)
@ -97,6 +102,11 @@ class MediaGoblinApp(object):
PluginManager().get_template_paths() PluginManager().get_template_paths()
) )
# Check if authentication plugin is enabled and respond accordingly.
self.auth = check_auth_enabled()
if not self.auth:
app_config['allow_comments'] = False
# Set up storage systems # Set up storage systems
self.public_store, self.queue_store = setup_storage() self.public_store, self.queue_store = setup_storage()
@ -186,6 +196,11 @@ class MediaGoblinApp(object):
request.urlgen = build_proxy request.urlgen = build_proxy
# Log user out if authentication_disabled
no_auth_logout(request)
request.notifications = notifications
mg_request.setup_user_in_request(request) mg_request.setup_user_in_request(request)
request.controller_name = None request.controller_name = None

View File

@ -13,3 +13,32 @@
# #
# 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/>.
from mediagoblin.tools.pluginapi import hook_handle, hook_runall
def get_user(**kwargs):
""" Takes a kwarg such as username and returns a user object """
return hook_handle("auth_get_user", **kwargs)
def create_user(register_form):
results = hook_runall("auth_create_user", register_form)
return results[0]
def extra_validation(register_form):
from mediagoblin.auth.tools import basic_extra_validation
extra_validation_passes = basic_extra_validation(register_form)
if False in hook_runall("auth_extra_validation", register_form):
extra_validation_passes = False
return extra_validation_passes
def gen_password_hash(raw_pass, extra_salt=None):
return hook_handle("auth_gen_password_hash", raw_pass, extra_salt)
def check_password(raw_pass, stored_hash, extra_salt=None):
return hook_handle("auth_check_password",
raw_pass, stored_hash, extra_salt)

View File

@ -20,32 +20,6 @@ from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
from mediagoblin.auth.tools import normalize_user_or_email_field from mediagoblin.auth.tools import normalize_user_or_email_field
class RegistrationForm(wtforms.Form):
username = wtforms.TextField(
_('Username'),
[wtforms.validators.Required(),
normalize_user_or_email_field(allow_email=False)])
password = wtforms.PasswordField(
_('Password'),
[wtforms.validators.Required(),
wtforms.validators.Length(min=5, max=1024)])
email = wtforms.TextField(
_('Email address'),
[wtforms.validators.Required(),
normalize_user_or_email_field(allow_user=False)])
class LoginForm(wtforms.Form):
username = wtforms.TextField(
_('Username or Email'),
[wtforms.validators.Required(),
normalize_user_or_email_field()])
password = wtforms.PasswordField(
_('Password'),
[wtforms.validators.Required(),
wtforms.validators.Length(min=5, max=1024)])
class ForgotPassForm(wtforms.Form): class ForgotPassForm(wtforms.Form):
username = wtforms.TextField( username = wtforms.TextField(
_('Username or email'), _('Username or email'),
@ -58,9 +32,6 @@ class ChangePassForm(wtforms.Form):
'Password', 'Password',
[wtforms.validators.Required(), [wtforms.validators.Required(),
wtforms.validators.Length(min=5, max=1024)]) wtforms.validators.Length(min=5, max=1024)])
userid = wtforms.HiddenField(
'',
[wtforms.validators.Required()])
token = wtforms.HiddenField( token = wtforms.HiddenField(
'', '',
[wtforms.validators.Required()]) [wtforms.validators.Required()])

View File

@ -14,19 +14,20 @@
# 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/>.
import uuid
import logging
import logging
import wtforms import wtforms
from sqlalchemy import or_ from sqlalchemy import or_
from mediagoblin import mg_globals from mediagoblin import mg_globals
from mediagoblin.auth import lib as auth_lib from mediagoblin.tools.crypto import get_timed_signer_url
from mediagoblin.db.models import User, Privilege from mediagoblin.db.models import User
from mediagoblin.tools.mail import (normalize_email, send_email, from mediagoblin.tools.mail import (normalize_email, send_email,
email_debug_message) email_debug_message)
from mediagoblin.tools.template import render_template from mediagoblin.tools.template import render_template
from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
from mediagoblin.tools.pluginapi import hook_handle
from mediagoblin import auth
_log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
@ -62,11 +63,12 @@ def normalize_user_or_email_field(allow_email=True, allow_user=True):
EMAIL_VERIFICATION_TEMPLATE = ( EMAIL_VERIFICATION_TEMPLATE = (
u"http://{host}{uri}?" u"{uri}?"
u"userid={userid}&token={verification_key}") u"token={verification_key}")
def send_verification_email(user, request): def send_verification_email(user, request, email=None,
rendered_email=None):
""" """
Send the verification email to users to activate their accounts. Send the verification email to users to activate their accounts.
@ -74,19 +76,24 @@ def send_verification_email(user, request):
- user: a user object - user: a user object
- request: the request - request: the request
""" """
if not email:
email = user.email
if not rendered_email:
verification_key = get_timed_signer_url('mail_verification_token') \
.dumps(user.id)
rendered_email = render_template( rendered_email = render_template(
request, 'mediagoblin/auth/verification_email.txt', request, 'mediagoblin/auth/verification_email.txt',
{'username': user.username, {'username': user.username,
'verification_url': EMAIL_VERIFICATION_TEMPLATE.format( 'verification_url': EMAIL_VERIFICATION_TEMPLATE.format(
host=request.host, uri=request.urlgen('mediagoblin.auth.verify_email',
uri=request.urlgen('mediagoblin.auth.verify_email'), qualified=True),
userid=unicode(user.id), verification_key=verification_key)})
verification_key=user.verification_key)})
# TODO: There is no error handling in place # TODO: There is no error handling in place
send_email( send_email(
mg_globals.app_config['email_sender_address'], mg_globals.app_config['email_sender_address'],
[user.email], [email],
# TODO # TODO
# Due to the distributed nature of GNU MediaGoblin, we should # Due to the distributed nature of GNU MediaGoblin, we should
# find a way to send some additional information about the # find a way to send some additional information about the
@ -96,11 +103,43 @@ def send_verification_email(user, request):
rendered_email) rendered_email)
EMAIL_FP_VERIFICATION_TEMPLATE = (
u"{uri}?"
u"token={fp_verification_key}")
def send_fp_verification_email(user, request):
"""
Send the verification email to users to change their password.
Args:
- user: a user object
- request: the request
"""
fp_verification_key = get_timed_signer_url('mail_verification_token') \
.dumps(user.id)
rendered_email = render_template(
request, 'mediagoblin/auth/fp_verification_email.txt',
{'username': user.username,
'verification_url': EMAIL_FP_VERIFICATION_TEMPLATE.format(
uri=request.urlgen('mediagoblin.auth.verify_forgot_password',
qualified=True),
fp_verification_key=fp_verification_key)})
# TODO: There is no error handling in place
send_email(
mg_globals.app_config['email_sender_address'],
[user.email],
'GNU MediaGoblin - Change forgotten password!',
rendered_email)
def basic_extra_validation(register_form, *args): def basic_extra_validation(register_form, *args):
users_with_username = User.query.filter_by( users_with_username = User.query.filter_by(
username=register_form.data['username']).count() username=register_form.username.data).count()
users_with_email = User.query.filter_by( users_with_email = User.query.filter_by(
email=register_form.data['email']).count() email=register_form.email.data).count()
extra_validation_passes = True extra_validation_passes = True
@ -118,17 +157,11 @@ def basic_extra_validation(register_form, *args):
def register_user(request, register_form): def register_user(request, register_form):
""" Handle user registration """ """ Handle user registration """
extra_validation_passes = basic_extra_validation(register_form) extra_validation_passes = auth.extra_validation(register_form)
if extra_validation_passes: if extra_validation_passes:
# Create the user # Create the user
user = User() user = auth.create_user(register_form)
user.username = register_form.data['username']
user.email = register_form.data['email']
user.pw_hash = auth_lib.bcrypt_gen_password_hash(
register_form.password.data)
user.verification_key = unicode(uuid.uuid4())
user.save()
# give the user the default privileges # give the user the default privileges
default_privileges = [ default_privileges = [
@ -151,17 +184,37 @@ def register_user(request, register_form):
return None return None
def check_login_simple(username, password, username_might_be_email=False): def check_login_simple(username, password):
search = (User.username == username) user = auth.get_user(username=username)
if username_might_be_email and ('@' in username):
search = or_(search, User.email == username)
user = User.query.filter(search).first()
if not user: if not user:
_log.info("User %r not found", username) _log.info("User %r not found", username)
auth_lib.fake_login_attempt() hook_handle("auth_fake_login_attempt")
return None return None
if not auth_lib.bcrypt_check_password(password, user.pw_hash): if not auth.check_password(password, user.pw_hash):
_log.warn("Wrong password for %r", username) _log.warn("Wrong password for %r", username)
return None return None
_log.info("Logging %r in", username) _log.info("Logging %r in", username)
return user return user
def check_auth_enabled():
if not hook_handle('authentication'):
_log.warning('No authentication is enabled')
return False
else:
return True
def no_auth_logout(request):
"""Log out the user if authentication_disabled, but don't delete the messages"""
if not mg_globals.app.auth and 'user_id' in request.session:
del request.session['user_id']
request.session.save()
def create_basic_user(form):
user = User()
user.username = form.username.data
user.email = form.email.data
user.save()
return user

View File

@ -14,36 +14,37 @@
# 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/>.
import uuid from itsdangerous import BadSignature
import datetime
from mediagoblin import messages, mg_globals from mediagoblin import messages, mg_globals
from mediagoblin.db.models import User, Privilege from mediagoblin.db.models import User, Privilege
from mediagoblin.tools.crypto import get_timed_signer_url
from mediagoblin.decorators import auth_enabled, allow_registration
from mediagoblin.tools.response import render_to_response, redirect, render_404 from mediagoblin.tools.response import render_to_response, redirect, render_404
from mediagoblin.tools.translate import pass_to_ugettext as _ from mediagoblin.tools.translate import pass_to_ugettext as _
from mediagoblin.tools.mail import email_debug_message from mediagoblin.tools.mail import email_debug_message
from mediagoblin.auth import lib as auth_lib from mediagoblin.tools.pluginapi import hook_handle
from mediagoblin.auth import forms as auth_forms from mediagoblin.auth import forms as auth_forms
from mediagoblin.auth.lib import send_fp_verification_email
from mediagoblin.auth.tools import (send_verification_email, register_user, from mediagoblin.auth.tools import (send_verification_email, register_user,
send_fp_verification_email,
check_login_simple) check_login_simple)
from mediagoblin import auth
@allow_registration
@auth_enabled
def register(request): def register(request):
"""The registration view. """The registration view.
Note that usernames will always be lowercased. Email domains are lowercased while Note that usernames will always be lowercased. Email domains are lowercased while
the first part remains case-sensitive. the first part remains case-sensitive.
""" """
# Redirects to indexpage if registrations are disabled if 'pass_auth' not in request.template_env.globals:
if not mg_globals.app_config["allow_registration"]: redirect_name = hook_handle('auth_no_pass_redirect')
messages.add_message( return redirect(request, 'mediagoblin.plugins.{0}.register'.format(
request, redirect_name))
messages.WARNING,
_('Sorry, registration is disabled on this instance.'))
return redirect(request, "index")
register_form = auth_forms.RegistrationForm(request.form) register_form = hook_handle("auth_get_registration_form", request)
if request.method == 'POST' and register_form.validate(): if request.method == 'POST' and register_form.validate():
# TODO: Make sure the user doesn't exist already # TODO: Make sure the user doesn't exist already
@ -59,28 +60,36 @@ def register(request):
return render_to_response( return render_to_response(
request, request,
'mediagoblin/auth/register.html', 'mediagoblin/auth/register.html',
{'register_form': register_form}) {'register_form': register_form,
'post_url': request.urlgen('mediagoblin.auth.register')})
@auth_enabled
def login(request): def login(request):
""" """
MediaGoblin login view. MediaGoblin login view.
If you provide the POST with 'next', it'll redirect to that view. If you provide the POST with 'next', it'll redirect to that view.
""" """
login_form = auth_forms.LoginForm(request.form) if 'pass_auth' not in request.template_env.globals:
redirect_name = hook_handle('auth_no_pass_redirect')
return redirect(request, 'mediagoblin.plugins.{0}.login'.format(
redirect_name))
login_form = hook_handle("auth_get_login_form", request)
login_failed = False login_failed = False
if request.method == 'POST': if request.method == 'POST':
username = login_form.username.data
username = login_form.data['username']
if login_form.validate(): if login_form.validate():
user = check_login_simple(username, login_form.password.data, True) user = check_login_simple(username, login_form.password.data)
if user: if user:
# set up login in session # set up login in session
if login_form.stay_logged_in.data:
request.session['stay_logged_in'] = True
request.session['user_id'] = unicode(user.id) request.session['user_id'] = unicode(user.id)
request.session.save() request.session.save()
@ -97,6 +106,7 @@ def login(request):
{'login_form': login_form, {'login_form': login_form,
'next': request.GET.get('next') or request.form.get('next'), 'next': request.GET.get('next') or request.form.get('next'),
'login_failed': login_failed, 'login_failed': login_failed,
'post_url': request.urlgen('mediagoblin.auth.login'),
'allow_registration': mg_globals.app_config["allow_registration"]}) 'allow_registration': mg_globals.app_config["allow_registration"]})
@ -115,12 +125,26 @@ def verify_email(request):
you are lucky :) you are lucky :)
""" """
# If we don't have userid and token parameters, we can't do anything; 404 # If we don't have userid and token parameters, we can't do anything; 404
if not 'userid' in request.GET or not 'token' in request.GET: if not 'token' in request.GET:
return render_404(request) return render_404(request)
user = User.query.filter_by(id=request.args['userid']).first() # Catch error if token is faked or expired
try:
token = get_timed_signer_url("mail_verification_token") \
.loads(request.GET['token'], max_age=10*24*3600)
except BadSignature:
messages.add_message(
request,
messages.ERROR,
_('The verification key or user id is incorrect.'))
if user and user.verification_key == unicode(request.GET['token']): return redirect(
request,
'index')
user = User.query.filter_by(id=int(token)).first()
if user and user.email_verified is False:
user.status = u'active' user.status = u'active'
user.email_verified = True user.email_verified = True
user.verification_key = None user.verification_key = None
@ -169,9 +193,6 @@ def resend_activation(request):
return redirect(request, "mediagoblin.user_pages.user_home", user=request.user['username']) return redirect(request, "mediagoblin.user_pages.user_home", user=request.user['username'])
request.user.verification_key = unicode(uuid.uuid4())
request.user.save()
email_debug_message(request) email_debug_message(request)
send_verification_email(request.user, request) send_verification_email(request.user, request)
@ -191,13 +212,16 @@ def forgot_password(request):
Sends an email with an url to renew forgotten password. Sends an email with an url to renew forgotten password.
Use GET querystring parameter 'username' to pre-populate the input field Use GET querystring parameter 'username' to pre-populate the input field
""" """
if not 'pass_auth' in request.template_env.globals:
return redirect(request, 'index')
fp_form = auth_forms.ForgotPassForm(request.form, fp_form = auth_forms.ForgotPassForm(request.form,
username=request.args.get('username')) username=request.args.get('username'))
if not (request.method == 'POST' and fp_form.validate()): if not (request.method == 'POST' and fp_form.validate()):
# Either GET request, or invalid form submitted. Display the template # Either GET request, or invalid form submitted. Display the template
return render_to_response(request, return render_to_response(request,
'mediagoblin/auth/forgot_password.html', {'fp_form': fp_form}) 'mediagoblin/auth/forgot_password.html', {'fp_form': fp_form,})
# If we are here: method == POST and form is valid. username casing # If we are here: method == POST and form is valid. username casing
# has been sanitized. Store if a user was found by email. We should # has been sanitized. Store if a user was found by email. We should
@ -238,11 +262,6 @@ def forgot_password(request):
# SUCCESS. Send reminder and return to login page # SUCCESS. Send reminder and return to login page
if user: if user:
user.fp_verification_key = unicode(uuid.uuid4())
user.fp_token_expire = datetime.datetime.now() + \
datetime.timedelta(days=10)
user.save()
email_debug_message(request) email_debug_message(request)
send_fp_verification_email(user, request) send_fp_verification_email(user, request)
@ -257,31 +276,44 @@ def verify_forgot_password(request):
""" """
# get form data variables, and specifically check for presence of token # get form data variables, and specifically check for presence of token
formdata = _process_for_token(request) formdata = _process_for_token(request)
if not formdata['has_userid_and_token']: if not formdata['has_token']:
return render_404(request) return render_404(request)
formdata_token = formdata['vars']['token']
formdata_userid = formdata['vars']['userid']
formdata_vars = formdata['vars'] formdata_vars = formdata['vars']
# check if it's a valid user id # Catch error if token is faked or expired
user = User.query.filter_by(id=formdata_userid).first() try:
if not user: token = get_timed_signer_url("mail_verification_token") \
return render_404(request) .loads(formdata_vars['token'], max_age=10*24*3600)
except BadSignature:
messages.add_message(
request,
messages.ERROR,
_('The verification key or user id is incorrect.'))
# check if we have a real user and correct token return redirect(
if ((user and user.fp_verification_key and request,
user.fp_verification_key == unicode(formdata_token) and 'index')
datetime.datetime.now() < user.fp_token_expire
and user.email_verified and user.status == 'active')): # check if it's a valid user id
user = User.query.filter_by(id=int(token)).first()
# no user in db
if not user:
messages.add_message(
request, messages.ERROR,
_('The user id is incorrect.'))
return redirect(
request, 'index')
# check if user active and has email verified
if user.email_verified and user.status == 'active':
cp_form = auth_forms.ChangePassForm(formdata_vars) cp_form = auth_forms.ChangePassForm(formdata_vars)
if request.method == 'POST' and cp_form.validate(): if request.method == 'POST' and cp_form.validate():
user.pw_hash = auth_lib.bcrypt_gen_password_hash( user.pw_hash = auth.gen_password_hash(
cp_form.password.data) cp_form.password.data)
user.fp_verification_key = None
user.fp_token_expire = None
user.save() user.save()
messages.add_message( messages.add_message(
@ -293,12 +325,22 @@ def verify_forgot_password(request):
return render_to_response( return render_to_response(
request, request,
'mediagoblin/auth/change_fp.html', 'mediagoblin/auth/change_fp.html',
{'cp_form': cp_form}) {'cp_form': cp_form,})
# in case there is a valid id but no user with that id in the db if not user.email_verified:
# or the token expired messages.add_message(
else: request, messages.ERROR,
return render_404(request) _('You need to verify your email before you can reset your'
' password.'))
if not user.status == 'active':
messages.add_message(
request, messages.ERROR,
_('You are no longer an active user. Please contact the system'
' admin to reactivate your accoutn.'))
return redirect(
request, 'index')
def _process_for_token(request): def _process_for_token(request):
@ -316,7 +358,6 @@ def _process_for_token(request):
formdata = { formdata = {
'vars': formdata_vars, 'vars': formdata_vars,
'has_userid_and_token': 'has_token': 'token' in formdata_vars}
'userid' in formdata_vars and 'token' in formdata_vars}
return formdata return formdata

View File

@ -5,12 +5,13 @@ html_title = string(default="GNU MediaGoblin")
# link to source for this MediaGoblin site # link to source for this MediaGoblin site
source_link = string(default="https://gitorious.org/mediagoblin/mediagoblin") source_link = string(default="https://gitorious.org/mediagoblin/mediagoblin")
# Enabled media types
media_types = string_list(default=list("mediagoblin.media_types.image"))
# database stuff # database stuff
sql_engine = string(default="sqlite:///%(here)s/mediagoblin.db") sql_engine = string(default="sqlite:///%(here)s/mediagoblin.db")
# This flag is used during testing to allow use of in-memory SQLite
# databases. It is not recommended to be used on a running instance.
run_migrations = boolean(default=False)
# Where temporary files used in processing and etc are kept # Where temporary files used in processing and etc are kept
workbench_path = string(default="%(here)s/user_dev/media/workbench") workbench_path = string(default="%(here)s/user_dev/media/workbench")
@ -22,9 +23,10 @@ direct_remote_path = string(default="/mgoblin_static/")
# set to false to enable sending notices # set to false to enable sending notices
email_debug_mode = boolean(default=True) email_debug_mode = boolean(default=True)
email_smtp_use_ssl = boolean(default=False)
email_sender_address = string(default="notice@mediagoblin.example.org") email_sender_address = string(default="notice@mediagoblin.example.org")
email_smtp_host = string(default='') email_smtp_host = string(default='')
email_smtp_port = integer(default=25) email_smtp_port = integer(default=0)
email_smtp_user = string(default=None) email_smtp_user = string(default=None)
email_smtp_pass = string(default=None) email_smtp_pass = string(default=None)

View File

@ -24,18 +24,6 @@ Session = scoped_session(sessionmaker())
class GMGTableBase(object): class GMGTableBase(object):
query = Session.query_property() query = Session.query_property()
@classmethod
def find(cls, query_dict):
return cls.query.filter_by(**query_dict)
@classmethod
def find_one(cls, query_dict):
return cls.query.filter_by(**query_dict).first()
@classmethod
def one(cls, query_dict):
return cls.find(query_dict).one()
def get(self, key): def get(self, key):
return getattr(self, key) return getattr(self, key)

View File

@ -29,7 +29,7 @@ class MigrationManager(object):
to the latest migrations, etc. to the latest migrations, etc.
""" """
def __init__(self, name, models, migration_registry, session, def __init__(self, name, models, foundations, migration_registry, session,
printer=simple_printer): printer=simple_printer):
""" """
Args: Args:
@ -40,6 +40,7 @@ class MigrationManager(object):
""" """
self.name = unicode(name) self.name = unicode(name)
self.models = models self.models = models
self.foundations = foundations
self.session = session self.session = session
self.migration_registry = migration_registry self.migration_registry = migration_registry
self._sorted_migrations = None self._sorted_migrations = None
@ -145,11 +146,11 @@ class MigrationManager(object):
Create the table foundations (default rows) as layed out in FOUNDATIONS Create the table foundations (default rows) as layed out in FOUNDATIONS
in mediagoblin.db.models in mediagoblin.db.models
""" """
from mediagoblin.db.models import FOUNDATIONS as MAIN_FOUNDATIONS for Model, rows in self.foundations.items():
for Model in MAIN_FOUNDATIONS.keys(): print u'\n + Laying foundations for %s table' % (Model.__name__)
for parameters in MAIN_FOUNDATIONS[Model]: for parameters in rows:
row = Model(*parameters) new_row = Model(**parameters)
row.save() new_row.save()
def create_new_migration_record(self): def create_new_migration_record(self):
""" """
@ -186,8 +187,7 @@ class MigrationManager(object):
if self.name == u'__main__': if self.name == u'__main__':
return u"main mediagoblin tables" return u"main mediagoblin tables"
else: else:
# TODO: Use the friendlier media manager "human readable" name return u'plugin "%s"' % self.name
return u'media type "%s"' % self.name
def init_or_migrate(self): def init_or_migrate(self):
""" """
@ -215,7 +215,6 @@ class MigrationManager(object):
self.init_tables() self.init_tables()
# auto-set at latest migration number # auto-set at latest migration number
self.create_new_migration_record() self.create_new_migration_record()
if self.name==u'__main__':
self.populate_table_foundations() self.populate_table_foundations()
self.printer(u"done.\n") self.printer(u"done.\n")

View File

@ -289,10 +289,99 @@ def unique_collections_slug(db):
db.commit() db.commit()
class ReportBase_v0(declarative_base()): @RegisterMigration(11, MIGRATIONS)
def drop_token_related_User_columns(db):
""" """
Drop unneeded columns from the User table after switching to using
itsdangerous tokens for email and forgot password verification.
"""
metadata = MetaData(bind=db.bind)
user_table = inspect_table(metadata, 'core__users')
"""
verification_key = user_table.columns['verification_key']
fp_verification_key = user_table.columns['fp_verification_key']
fp_token_expire = user_table.columns['fp_token_expire']
verification_key.drop()
fp_verification_key.drop()
fp_token_expire.drop()
db.commit()
class CommentSubscription_v0(declarative_base()):
__tablename__ = 'core__comment_subscriptions'
id = Column(Integer, primary_key=True)
created = Column(DateTime, nullable=False, default=datetime.datetime.now)
media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False)
user_id = Column(Integer, ForeignKey(User.id), nullable=False)
notify = Column(Boolean, nullable=False, default=True)
send_email = Column(Boolean, nullable=False, default=True)
class Notification_v0(declarative_base()):
__tablename__ = 'core__notifications'
id = Column(Integer, primary_key=True)
type = Column(Unicode)
created = Column(DateTime, nullable=False, default=datetime.datetime.now)
user_id = Column(Integer, ForeignKey(User.id), nullable=False,
index=True)
seen = Column(Boolean, default=lambda: False, index=True)
class CommentNotification_v0(Notification_v0):
__tablename__ = 'core__comment_notifications'
id = Column(Integer, ForeignKey(Notification_v0.id), primary_key=True)
subject_id = Column(Integer, ForeignKey(MediaComment.id))
class ProcessingNotification_v0(Notification_v0):
__tablename__ = 'core__processing_notifications'
id = Column(Integer, ForeignKey(Notification_v0.id), primary_key=True)
subject_id = Column(Integer, ForeignKey(MediaEntry.id))
@RegisterMigration(12, MIGRATIONS)
def add_new_notification_tables(db):
metadata = MetaData(bind=db.bind)
user_table = inspect_table(metadata, 'core__users')
mediaentry_table = inspect_table(metadata, 'core__media_entries')
mediacomment_table = inspect_table(metadata, 'core__media_comments')
CommentSubscription_v0.__table__.create(db.bind)
Notification_v0.__table__.create(db.bind)
CommentNotification_v0.__table__.create(db.bind)
ProcessingNotification_v0.__table__.create(db.bind)
@RegisterMigration(13, MIGRATIONS)
def pw_hash_nullable(db):
"""Make pw_hash column nullable"""
metadata = MetaData(bind=db.bind)
user_table = inspect_table(metadata, "core__users")
user_table.c.pw_hash.alter(nullable=True)
# sqlite+sqlalchemy seems to drop this constraint during the
# migration, so we add it back here for now a bit manually.
if db.bind.url.drivername == 'sqlite':
constraint = UniqueConstraint('username', table=user_table)
constraint.create()
class ReportBase_v0(declarative_base()):
__tablename__ = 'core__reports' __tablename__ = 'core__reports'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
reporter_id = Column(Integer, ForeignKey(User.id), nullable=False) reporter_id = Column(Integer, ForeignKey(User.id), nullable=False)
@ -302,7 +391,6 @@ class ReportBase_v0(declarative_base()):
discriminator = Column('type', Unicode(50)) discriminator = Column('type', Unicode(50))
__mapper_args__ = {'polymorphic_on': discriminator} __mapper_args__ = {'polymorphic_on': discriminator}
class CommentReport_v0(ReportBase_v0): class CommentReport_v0(ReportBase_v0):
__tablename__ = 'core__reports_on_comments' __tablename__ = 'core__reports_on_comments'
__mapper_args__ = {'polymorphic_identity': 'comment_report'} __mapper_args__ = {'polymorphic_identity': 'comment_report'}
@ -316,14 +404,13 @@ class MediaReport_v0(ReportBase_v0):
__mapper_args__ = {'polymorphic_identity': 'media_report'} __mapper_args__ = {'polymorphic_identity': 'media_report'}
id = Column('id',Integer, ForeignKey('core__reports.id'), primary_key=True) id = Column('id',Integer, ForeignKey('core__reports.id'), primary_key=True)
media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False) media_entry_id = Column(Integer, ForeignKey(MediaEntry.i
class ArchivedReport_v0(ReportBase_v0): class ArchivedReport_v0(ReportBase_v0):
__tablename__ = 'core__reports_archived' __tablename__ = 'core__reports_archived'
__mapper_args__ = {'polymorphic_identity': 'archived_report'} __mapper_args__ = {'polymorphic_identity': 'archived_report'}
id = Column('id',Integer, ForeignKey('core__reports.id')) id = Column('id',Integer, ForeignKey('core__reports.id'))
media_entry_id = Column(Integer, ForeignKey(MediaEntry.id)) media_entry_id = Column(Integer, ForeignKey(MediaEntry.id))
comment_id = Column(Integer, ForeignKey(MediaComment.id)) comment_id = Column(Integer, ForeignKey(MediaComment.id))
resolver_id = Column(Integer, ForeignKey(User.id), nullable=False) resolver_id = Column(Integer, ForeignKey(User.id), nullable=False)
@ -344,7 +431,6 @@ class Privilege_v0(declarative_base()):
class PrivilegeUserAssociation_v0(declarative_base()): class PrivilegeUserAssociation_v0(declarative_base()):
__tablename__ = 'core__privileges_users' __tablename__ = 'core__privileges_users'
group_id = Column( group_id = Column(
'core__privilege_id', 'core__privilege_id',
Integer, Integer,
@ -356,7 +442,7 @@ class PrivilegeUserAssociation_v0(declarative_base()):
ForeignKey(Privilege.id), ForeignKey(Privilege.id),
primary_key=True) primary_key=True)
@RegisterMigration(11, MIGRATIONS) @RegisterMigration(14, MIGRATIONS)
def create_moderation_tables(db): def create_moderation_tables(db):
ReportBase_v0.__table__.create(db.bind) ReportBase_v0.__table__.create(db.bind)
CommentReport_v0.__table__.create(db.bind) CommentReport_v0.__table__.create(db.bind)

View File

@ -29,13 +29,14 @@ real objects.
import uuid import uuid
import re import re
import datetime from datetime import datetime
from werkzeug.utils import cached_property from werkzeug.utils import cached_property
from mediagoblin import mg_globals from mediagoblin import mg_globals
from mediagoblin.media_types import get_media_managers, FileTypeNotSupported from mediagoblin.media_types import FileTypeNotSupported
from mediagoblin.tools import common, licenses from mediagoblin.tools import common, licenses
from mediagoblin.tools.pluginapi import hook_handle
from mediagoblin.tools.text import cleaned_markdown_conversion from mediagoblin.tools.text import cleaned_markdown_conversion
from mediagoblin.tools.url import slugify from mediagoblin.tools.url import slugify
@ -201,14 +202,14 @@ class MediaEntryMixin(GenerateSlugMixin):
Raises FileTypeNotSupported in case no such manager is enabled Raises FileTypeNotSupported in case no such manager is enabled
""" """
# TODO, we should be able to make this a simple lookup rather manager = hook_handle(('media_manager', self.media_type))
# than iterating through all media managers. if manager:
for media_type, manager in get_media_managers():
if media_type == self.media_type:
return manager(self) return manager(self)
# Not found? Then raise an error # Not found? Then raise an error
raise FileTypeNotSupported( raise FileTypeNotSupported(
"MediaManager not in enabled types. Check media_types in config?") "MediaManager not in enabled types. Check media_type plugins are"
" enabled in config?")
def get_fail_exception(self): def get_fail_exception(self):
""" """
@ -287,6 +288,13 @@ class MediaCommentMixin(object):
""" """
return cleaned_markdown_conversion(self.content) return cleaned_markdown_conversion(self.content)
def __repr__(self):
return '<{klass} #{id} {author} "{comment}">'.format(
klass=self.__class__.__name__,
id=self.id,
author=self.get_author,
comment=self.content)
class CollectionMixin(GenerateSlugMixin): class CollectionMixin(GenerateSlugMixin):
def check_slug_used(self, slug): def check_slug_used(self, slug):

View File

@ -24,16 +24,16 @@ import datetime
from sqlalchemy import Column, Integer, Unicode, UnicodeText, DateTime, \ from sqlalchemy import Column, Integer, Unicode, UnicodeText, DateTime, \
Boolean, ForeignKey, UniqueConstraint, PrimaryKeyConstraint, \ Boolean, ForeignKey, UniqueConstraint, PrimaryKeyConstraint, \
SmallInteger SmallInteger
from sqlalchemy.orm import relationship, backref from sqlalchemy.orm import relationship, backref, with_polymorphic
from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.sql.expression import desc from sqlalchemy.sql.expression import desc
from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.util import memoized_property from sqlalchemy.util import memoized_property
from sqlalchemy.schema import Table
from mediagoblin.db.extratypes import PathTupleWithSlashes, JSONEncoded from mediagoblin.db.extratypes import PathTupleWithSlashes, JSONEncoded
from mediagoblin.db.base import Base, DictReadAttrProxy from mediagoblin.db.base import Base, DictReadAttrProxy
from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin, CollectionMixin, CollectionItemMixin from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, \
MediaCommentMixin, CollectionMixin, CollectionItemMixin
from mediagoblin.tools.files import delete_media_files from mediagoblin.tools.files import delete_media_files
from mediagoblin.tools.common import import_component from mediagoblin.tools.common import import_component
@ -239,8 +239,8 @@ class MediaEntry(Base, MediaEntryMixin):
This will *not* automatically delete unused collections, which This will *not* automatically delete unused collections, which
can remain empty... can remain empty...
:keyword del_orphan_tags: True/false if we delete unused Tags too :param del_orphan_tags: True/false if we delete unused Tags too
:keyword commit: True/False if this should end the db transaction""" :param commit: True/False if this should end the db transaction"""
# User's CollectionItems are automatically deleted via "cascade". # User's CollectionItems are automatically deleted via "cascade".
# Comments on this Media are deleted by cascade, hopefully. # Comments on this Media are deleted by cascade, hopefully.
@ -393,6 +393,10 @@ class MediaComment(Base, MediaCommentMixin):
backref=backref("posted_comments", backref=backref("posted_comments",
lazy="dynamic", lazy="dynamic",
cascade="all, delete-orphan")) cascade="all, delete-orphan"))
get_entry = relationship(MediaEntry,
backref=backref("comments",
lazy="dynamic",
cascade="all, delete-orphan"))
# Cascade: Comments are somewhat owned by their MediaEntry. # Cascade: Comments are somewhat owned by their MediaEntry.
# So do the full thing. # So do the full thing.
@ -484,6 +488,92 @@ class ProcessingMetaData(Base):
"""A dict like view on this object""" """A dict like view on this object"""
return DictReadAttrProxy(self) return DictReadAttrProxy(self)
class CommentSubscription(Base):
__tablename__ = 'core__comment_subscriptions'
id = Column(Integer, primary_key=True)
created = Column(DateTime, nullable=False, default=datetime.datetime.now)
media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False)
media_entry = relationship(MediaEntry,
backref=backref('comment_subscriptions',
cascade='all, delete-orphan'))
user_id = Column(Integer, ForeignKey(User.id), nullable=False)
user = relationship(User,
backref=backref('comment_subscriptions',
cascade='all, delete-orphan'))
notify = Column(Boolean, nullable=False, default=True)
send_email = Column(Boolean, nullable=False, default=True)
def __repr__(self):
return ('<{classname} #{id}: {user} {media} notify: '
'{notify} email: {email}>').format(
id=self.id,
classname=self.__class__.__name__,
user=self.user,
media=self.media_entry,
notify=self.notify,
email=self.send_email)
class Notification(Base):
__tablename__ = 'core__notifications'
id = Column(Integer, primary_key=True)
type = Column(Unicode)
created = Column(DateTime, nullable=False, default=datetime.datetime.now)
user_id = Column(Integer, ForeignKey('core__users.id'), nullable=False,
index=True)
seen = Column(Boolean, default=lambda: False, index=True)
user = relationship(
User,
backref=backref('notifications', cascade='all, delete-orphan'))
__mapper_args__ = {
'polymorphic_identity': 'notification',
'polymorphic_on': type
}
def __repr__(self):
return '<{klass} #{id}: {user}: {subject} ({seen})>'.format(
id=self.id,
klass=self.__class__.__name__,
user=self.user,
subject=getattr(self, 'subject', None),
seen='unseen' if not self.seen else 'seen')
class CommentNotification(Notification):
__tablename__ = 'core__comment_notifications'
id = Column(Integer, ForeignKey(Notification.id), primary_key=True)
subject_id = Column(Integer, ForeignKey(MediaComment.id))
subject = relationship(
MediaComment,
backref=backref('comment_notifications', cascade='all, delete-orphan'))
__mapper_args__ = {
'polymorphic_identity': 'comment_notification'
}
class ProcessingNotification(Notification):
__tablename__ = 'core__processing_notifications'
id = Column(Integer, ForeignKey(Notification.id), primary_key=True)
subject_id = Column(Integer, ForeignKey(MediaEntry.id))
subject = relationship(
MediaEntry,
backref=backref('processing_notifications',
cascade='all, delete-orphan'))
__mapper_args__ = {
'polymorphic_identity': 'processing_notification'
}
class ReportBase(Base): class ReportBase(Base):
""" """
@ -672,20 +762,38 @@ class PrivilegeUserAssociation(Base):
ForeignKey(Privilege.id), ForeignKey(Privilege.id),
primary_key=True) primary_key=True)
with_polymorphic(
Notification,
[ProcessingNotification, CommentNotification])
privilege_foundations = [[u'admin'], [u'moderator'], [u'uploader'],[u'reporter'], [u'commenter'] ,[u'active']]
MODELS = [ MODELS = [
User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem,
MediaFile, FileKeynames, MediaAttachmentFile, ProcessingMetaData, ReportBase, MediaFile, FileKeynames, MediaAttachmentFile, ProcessingMetaData, ReportBase,
CommentReport, MediaReport, UserBan, Privilege, PrivilegeUserAssociation, CommentReport, MediaReport, UserBan, Privilege, PrivilegeUserAssociation,
ArchivedReport] ArchivedReport, Notification, CommentNotification,
ProcessingNotification, CommentSubscription]
# Foundations are the default rows that are created immediately after the tables are initialized. Each entry to """
# this dictionary should be in the format of Foundations are the default rows that are created immediately after the tables
# ModelObject:List of Rows are initialized. Each entry to this dictionary should be in the format of:
# (Each Row must be a list of parameters that can create and instance of the ModelObject) ModelConstructorObject:List of Dictionaries
# (Each Dictionary represents a row on the Table to be created, containing each
of the columns' names as a key string, and each of the columns' values as a
value)
ex. [NOTE THIS IS NOT BASED OFF OF OUR USER TABLE]
user_foundations = [{'name':u'Joanna', 'age':24},
{'name':u'Andrea', 'age':41}]
FOUNDATIONS = {User:user_foundations}
"""
privilege_foundations = [{'privilege_name':u'admin'},
{'privilege_name':u'moderator'},
{'privilege_name':u'uploader'},
{'privilege_name':u'reporter'},
{'privilege_name':u'commenter'},
{'privilege_name':u'active'}]
FOUNDATIONS = {Privilege:privilege_foundations} FOUNDATIONS = {Privilege:privilege_foundations}
###################################################### ######################################################

View File

@ -18,6 +18,29 @@
TODO: indexes on foreignkeys, where useful. TODO: indexes on foreignkeys, where useful.
""" """
###########################################################################
# WHAT IS THIS FILE?
# ------------------
#
# Upon occasion, someone runs into this file and wonders why we have
# both a models.py and a models_v0.py.
#
# The short of it is: you can ignore this file.
#
# The long version is, in two parts:
#
# - We used to use MongoDB, then we switched to SQL and SQLAlchemy.
# We needed to convert peoples' databases; the script we had would
# switch them to the first version right after Mongo, convert over
# all their tables, then run any migrations that were added after.
#
# - That script is now removed, but there is some discussion of
# writing a test that would set us at the first SQL migration and
# run everything after. If we wrote that, this file would still be
# useful. But for now, it's legacy!
#
###########################################################################
import datetime import datetime
import sys import sys

View File

@ -52,10 +52,6 @@ class DatabaseMaster(object):
def load_models(app_config): def load_models(app_config):
import mediagoblin.db.models import mediagoblin.db.models
for media_type in app_config['media_types']:
_log.debug("Loading %s.models", media_type)
__import__(media_type + ".models")
for plugin in mg_globals.global_config.get('plugins', {}).keys(): for plugin in mg_globals.global_config.get('plugins', {}).keys():
_log.debug("Loading %s.models", plugin) _log.debug("Loading %s.models", plugin)
try: try:

View File

@ -25,7 +25,7 @@ from mediagoblin.db.models import (MediaEntry, Tag, MediaTag, Collection, \
def atomic_update(table, query_dict, update_values): def atomic_update(table, query_dict, update_values):
table.find(query_dict).update(update_values, table.query.filter_by(**query_dict).update(update_values,
synchronize_session=False) synchronize_session=False)
Session.commit() Session.commit()

View File

@ -21,9 +21,11 @@ from werkzeug.exceptions import Forbidden, NotFound
from werkzeug.urls import url_quote from werkzeug.urls import url_quote
from mediagoblin import mg_globals as mgg from mediagoblin import mg_globals as mgg
from mediagoblin.db.models import MediaEntry, User, MediaComment, Privilege, \ from mediagoblin import messages
from mediagoblin.db.models import MediaEntry, User, MediaComment,
UserBan UserBan
from mediagoblin.tools.response import redirect, render_404, render_user_banned from mediagoblin.tools.response import redirect, render_404
from mediagoblin.tools.translate import pass_to_ugettext as _
def require_active_login(controller): def require_active_login(controller):
@ -107,8 +109,8 @@ def user_may_alter_collection(controller):
""" """
@wraps(controller) @wraps(controller)
def wrapper(request, *args, **kwargs): def wrapper(request, *args, **kwargs):
creator_id = request.db.User.find_one( creator_id = request.db.User.query.filter_by(
{'username': request.matchdict['user']}).id username=request.matchdict['user']).first().id
if not (request.user.is_admin or if not (request.user.is_admin or
request.user.id == creator_id): request.user.id == creator_id):
raise Forbidden() raise Forbidden()
@ -182,15 +184,15 @@ def get_user_collection(controller):
""" """
@wraps(controller) @wraps(controller)
def wrapper(request, *args, **kwargs): def wrapper(request, *args, **kwargs):
user = request.db.User.find_one( user = request.db.User.query.filter_by(
{'username': request.matchdict['user']}) username=request.matchdict['user']).first()
if not user: if not user:
return render_404(request) return render_404(request)
collection = request.db.Collection.find_one( collection = request.db.Collection.query.filter_by(
{'slug': request.matchdict['collection'], slug=request.matchdict['collection'],
'creator': user.id}) creator=user.id).first()
# Still no collection? Okay, 404. # Still no collection? Okay, 404.
if not collection: if not collection:
@ -207,14 +209,14 @@ def get_user_collection_item(controller):
""" """
@wraps(controller) @wraps(controller)
def wrapper(request, *args, **kwargs): def wrapper(request, *args, **kwargs):
user = request.db.User.find_one( user = request.db.User.query.filter_by(
{'username': request.matchdict['user']}) username=request.matchdict['user']).first()
if not user: if not user:
return render_404(request) return render_404(request)
collection_item = request.db.CollectionItem.find_one( collection_item = request.db.CollectionItem.query.filter_by(
{'id': request.matchdict['collection_item'] }) id=request.matchdict['collection_item']).first()
# Still no collection item? Okay, 404. # Still no collection item? Okay, 404.
if not collection_item: if not collection_item:
@ -247,6 +249,21 @@ def get_media_entry_by_id(controller):
return wrapper return wrapper
def allow_registration(controller):
""" Decorator for if registration is enabled"""
@wraps(controller)
def wrapper(request, *args, **kwargs):
if not mgg.app_config["allow_registration"]:
messages.add_message(
request,
messages.WARNING,
_('Sorry, registration is disabled on this instance.'))
return redirect(request, "index")
return controller(request, *args, **kwargs)
return wrapper
def get_media_comment_by_id(controller): def get_media_comment_by_id(controller):
""" """
Pass in a MediaComment based off of a url component Pass in a MediaComment based off of a url component
@ -264,15 +281,26 @@ def get_media_comment_by_id(controller):
return wrapper return wrapper
def auth_enabled(controller):
"""Decorator for if an auth plugin is enabled"""
@wraps(controller)
def wrapper(request, *args, **kwargs):
if not mgg.app.auth:
messages.add_message(
request,
messages.WARNING,
_('Sorry, authentication is disabled on this instance.'))
return redirect(request, 'index')
return controller(request, *args, **kwargs)
return wrapper
def get_workbench(func): def get_workbench(func):
"""Decorator, passing in a workbench as kwarg which is cleaned up afterwards""" """Decorator, passing in a workbench as kwarg which is cleaned up afterwards"""
@wraps(func) @wraps(func)
def new_func(*args, **kwargs): def new_func(*args, **kwargs):
with mgg.workbench_manager.create() as workbench: with mgg.workbench_manager.create() as workbench:
return func(*args, workbench=workbench, **kwargs) return func(*args, workbench=workbench, **kwargs)
return new_func return new_func
def require_admin_or_moderator_login(controller): def require_admin_or_moderator_login(controller):

View File

@ -16,9 +16,11 @@
import wtforms import wtforms
from mediagoblin.tools.text import tag_length_validator, TOO_LONG_TAG_WARNING from mediagoblin.tools.text import tag_length_validator
from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
from mediagoblin.tools.licenses import licenses_as_choices from mediagoblin.tools.licenses import licenses_as_choices
from mediagoblin.auth.forms import normalize_user_or_email_field
class EditForm(wtforms.Form): class EditForm(wtforms.Form):
title = wtforms.TextField( title = wtforms.TextField(
@ -59,6 +61,12 @@ class EditProfileForm(wtforms.Form):
class EditAccountForm(wtforms.Form): class EditAccountForm(wtforms.Form):
new_email = wtforms.TextField(
_('New email address'),
[wtforms.validators.Optional(),
normalize_user_or_email_field(allow_user=False)])
wants_comment_notification = wtforms.BooleanField(
description=_("Email me when others comment on my media"))
license_preference = wtforms.SelectField( license_preference = wtforms.SelectField(
_('License preference'), _('License preference'),
[ [
@ -67,8 +75,6 @@ class EditAccountForm(wtforms.Form):
], ],
choices=licenses_as_choices(), choices=licenses_as_choices(),
description=_('This will be your default license on upload forms.')) description=_('This will be your default license on upload forms.'))
wants_comment_notification = wtforms.BooleanField(
label=_("Email me when others comment on my media"))
class EditAttachmentsForm(wtforms.Form): class EditAttachmentsForm(wtforms.Form):

View File

@ -26,3 +26,5 @@ add_route('mediagoblin.edit.delete_account', '/edit/account/delete/',
'mediagoblin.edit.views:delete_account') 'mediagoblin.edit.views:delete_account')
add_route('mediagoblin.edit.pass', '/edit/password/', add_route('mediagoblin.edit.pass', '/edit/password/',
'mediagoblin.edit.views:change_pass') 'mediagoblin.edit.views:change_pass')
add_route('mediagoblin.edit.verify_email', '/edit/verify_email/',
'mediagoblin.edit.views:verify_email')

View File

@ -16,25 +16,31 @@
from datetime import datetime from datetime import datetime
from itsdangerous import BadSignature
from werkzeug.exceptions import Forbidden from werkzeug.exceptions import Forbidden
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from mediagoblin import messages from mediagoblin import messages
from mediagoblin import mg_globals from mediagoblin import mg_globals
from mediagoblin.auth import lib as auth_lib from mediagoblin import auth
from mediagoblin.auth import tools as auth_tools
from mediagoblin.edit import forms from mediagoblin.edit import forms
from mediagoblin.edit.lib import may_edit_media from mediagoblin.edit.lib import may_edit_media
from mediagoblin.decorators import (require_active_login, active_user_from_url, from mediagoblin.decorators import (require_active_login, active_user_from_url,
get_media_entry_by_id, get_media_entry_by_id, user_may_alter_collection,
user_may_alter_collection, get_user_collection) get_user_collection)
from mediagoblin.tools.response import render_to_response, \ from mediagoblin.tools.crypto import get_timed_signer_url
redirect, redirect_obj from mediagoblin.tools.mail import email_debug_message
from mediagoblin.tools.response import (render_to_response,
redirect, redirect_obj, render_404)
from mediagoblin.tools.translate import pass_to_ugettext as _ from mediagoblin.tools.translate import pass_to_ugettext as _
from mediagoblin.tools.template import render_template
from mediagoblin.tools.text import ( from mediagoblin.tools.text import (
convert_to_tag_list_of_dicts, media_tags_as_string) convert_to_tag_list_of_dicts, media_tags_as_string)
from mediagoblin.tools.url import slugify from mediagoblin.tools.url import slugify
from mediagoblin.db.util import check_media_slug_used, check_collection_slug_used from mediagoblin.db.util import check_media_slug_used, check_collection_slug_used
from mediagoblin.db.models import User
import mimetypes import mimetypes
@ -212,6 +218,10 @@ def edit_profile(request, url_user=None):
{'user': user, {'user': user,
'form': form}) 'form': form})
EMAIL_VERIFICATION_TEMPLATE = (
u'{uri}?'
u'token={verification_key}')
@require_active_login @require_active_login
def edit_account(request): def edit_account(request):
@ -220,20 +230,15 @@ def edit_account(request):
wants_comment_notification=user.wants_comment_notification, wants_comment_notification=user.wants_comment_notification,
license_preference=user.license_preference) license_preference=user.license_preference)
if request.method == 'POST': if request.method == 'POST' and form.validate():
form_validated = form.validate() user.wants_comment_notification = form.wants_comment_notification.data
if form_validated and \ user.license_preference = form.license_preference.data
form.wants_comment_notification.validate(form):
user.wants_comment_notification = \
form.wants_comment_notification.data
if form_validated and \ if form.new_email.data:
form.license_preference.validate(form): _update_email(request, form, user)
user.license_preference = \
form.license_preference.data
if form_validated and not form.errors: if not form.errors:
user.save() user.save()
messages.add_message(request, messages.add_message(request,
messages.SUCCESS, messages.SUCCESS,
@ -300,9 +305,9 @@ def edit_collection(request, collection):
form.slug.data, collection.id) form.slug.data, collection.id)
# Make sure there isn't already a Collection with this title # Make sure there isn't already a Collection with this title
existing_collection = request.db.Collection.find_one({ existing_collection = request.db.Collection.query.filter_by(
'creator': request.user.id, creator=request.user.id,
'title':form.title.data}) title=form.title.data).first()
if existing_collection and existing_collection.id != collection.id: if existing_collection and existing_collection.id != collection.id:
messages.add_message( messages.add_message(
@ -337,12 +342,16 @@ def edit_collection(request, collection):
@require_active_login @require_active_login
def change_pass(request): def change_pass(request):
# If no password authentication, no need to change your password
if 'pass_auth' not in request.template_env.globals:
return redirect(request, 'index')
form = forms.ChangePassForm(request.form) form = forms.ChangePassForm(request.form)
user = request.user user = request.user
if request.method == 'POST' and form.validate(): if request.method == 'POST' and form.validate():
if not auth_lib.bcrypt_check_password( if not auth.check_password(
form.old_password.data, user.pw_hash): form.old_password.data, user.pw_hash):
form.old_password.errors.append( form.old_password.errors.append(
_('Wrong password')) _('Wrong password'))
@ -354,7 +363,7 @@ def change_pass(request):
'user': user}) 'user': user})
# Password matches # Password matches
user.pw_hash = auth_lib.bcrypt_gen_password_hash( user.pw_hash = auth.gen_password_hash(
form.new_password.data) form.new_password.data)
user.save() user.save()
@ -369,3 +378,77 @@ def change_pass(request):
'mediagoblin/edit/change_pass.html', 'mediagoblin/edit/change_pass.html',
{'form': form, {'form': form,
'user': user}) 'user': user})
def verify_email(request):
"""
Email verification view for changing email address
"""
# If no token, we can't do anything
if not 'token' in request.GET:
return render_404(request)
# Catch error if token is faked or expired
token = None
try:
token = get_timed_signer_url("mail_verification_token") \
.loads(request.GET['token'], max_age=10*24*3600)
except BadSignature:
messages.add_message(
request,
messages.ERROR,
_('The verification key or user id is incorrect.'))
return redirect(
request,
'index')
user = User.query.filter_by(id=int(token['user'])).first()
if user:
user.email = token['email']
user.save()
messages.add_message(
request,
messages.SUCCESS,
_('Your email address has been verified.'))
else:
messages.add_message(
request,
messages.ERROR,
_('The verification key or user id is incorrect.'))
return redirect(
request, 'mediagoblin.user_pages.user_home',
user=user.username)
def _update_email(request, form, user):
new_email = form.new_email.data
users_with_email = User.query.filter_by(
email=new_email).count()
if users_with_email:
form.new_email.errors.append(
_('Sorry, a user with that email address'
' already exists.'))
elif not users_with_email:
verification_key = get_timed_signer_url(
'mail_verification_token').dumps({
'user': user.id,
'email': new_email})
rendered_email = render_template(
request, 'mediagoblin/edit/verification.txt',
{'username': user.username,
'verification_url': EMAIL_VERIFICATION_TEMPLATE.format(
uri=request.urlgen('mediagoblin.edit.verify_email',
qualified=True),
verification_key=verification_key)})
email_debug_message(request)
auth_tools.send_verification_email(user, request, new_email,
rendered_email)

View File

@ -32,17 +32,18 @@ def dbupdate_parse_setup(subparser):
class DatabaseData(object): class DatabaseData(object):
def __init__(self, name, models, migrations): def __init__(self, name, models, foundations, migrations):
self.name = name self.name = name
self.models = models self.models = models
self.foundations = foundations
self.migrations = migrations self.migrations = migrations
def make_migration_manager(self, session): def make_migration_manager(self, session):
return MigrationManager( return MigrationManager(
self.name, self.models, self.migrations, session) self.name, self.models, self.foundations, self.migrations, session)
def gather_database_data(media_types, plugins): def gather_database_data(plugins):
""" """
Gather all database data relevant to the extensions we have Gather all database data relevant to the extensions we have
installed so we can do migrations and table initialization. installed so we can do migrations and table initialization.
@ -54,17 +55,11 @@ def gather_database_data(media_types, plugins):
# Add main first # Add main first
from mediagoblin.db.models import MODELS as MAIN_MODELS from mediagoblin.db.models import MODELS as MAIN_MODELS
from mediagoblin.db.migrations import MIGRATIONS as MAIN_MIGRATIONS from mediagoblin.db.migrations import MIGRATIONS as MAIN_MIGRATIONS
from mediagoblin.db.models import FOUNDATIONS as MAIN_FOUNDATIONS
managed_dbdata.append( managed_dbdata.append(
DatabaseData( DatabaseData(
u'__main__', MAIN_MODELS, MAIN_MIGRATIONS)) u'__main__', MAIN_MODELS, MAIN_FOUNDATIONS, 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))
for plugin in plugins: for plugin in plugins:
try: try:
@ -90,13 +85,26 @@ forgotten to add it? ({1})'.format(plugin, exc))
migrations = {} migrations = {}
except AttributeError as exc: except AttributeError as exc:
_log.debug('Cloud not find MIGRATIONS in {0}.migrations, have you \ _log.debug('Could not find MIGRATIONS in {0}.migrations, have you \
forgotten to add it? ({1})'.format(plugin, exc)) forgotten to add it? ({1})'.format(plugin, exc))
migrations = {} migrations = {}
try:
foundations = import_component('{0}.models:FOUNDATIONS'.format(plugin))
except ImportError as exc:
_log.debug('No foundations found for {0}: {1}'.format(
plugin,
exc))
foundations = []
except AttributeError as exc:
_log.debug('Could not find FOUNDATIONS in {0}.models, have you \
forgotten to add it? ({1})'.format(plugin, exc))
foundations = {}
if models: if models:
managed_dbdata.append( managed_dbdata.append(
DatabaseData(plugin, models, migrations)) DatabaseData(plugin, models, foundations, migrations))
return managed_dbdata return managed_dbdata
@ -110,13 +118,24 @@ def run_dbupdate(app_config, global_config):
in the future, plugins) in the future, plugins)
""" """
# Gather information from all media managers / projects
dbdatas = gather_database_data(
app_config['media_types'],
global_config.get('plugins', {}).keys())
# Set up the database # Set up the database
db = setup_connection_and_db_from_config(app_config, migrations=True) db = setup_connection_and_db_from_config(app_config, migrations=True)
#Run the migrations
run_all_migrations(db, app_config, global_config)
def run_all_migrations(db, app_config, global_config):
"""
Initializes or migrates a database that already has a
connection setup and also initializes or migrates all
extensions based on the config files.
It can be used to initialize an in-memory database for
testing.
"""
# Gather information from all media managers / projects
dbdatas = gather_database_data(
global_config.get('plugins', {}).keys())
Session = sessionmaker(bind=db.engine) Session = sessionmaker(bind=db.engine)

View File

@ -63,7 +63,7 @@ def _import_media(db, args):
# TODO: Add import of queue files # TODO: Add import of queue files
queue_cache = BasicFileStorage(args._cache_path['queue']) queue_cache = BasicFileStorage(args._cache_path['queue'])
for entry in db.MediaEntry.find(): for entry in db.MediaEntry.query.filter_by():
for name, path in entry.media_files.items(): for name, path in entry.media_files.items():
_log.info('Importing: {0} - {1}'.format( _log.info('Importing: {0} - {1}'.format(
entry.title.encode('ascii', 'replace'), entry.title.encode('ascii', 'replace'),
@ -204,7 +204,7 @@ def _export_media(db, args):
# TODO: Add export of queue files # TODO: Add export of queue files
queue_cache = BasicFileStorage(args._cache_path['queue']) queue_cache = BasicFileStorage(args._cache_path['queue'])
for entry in db.MediaEntry.find(): for entry in db.MediaEntry.query.filter_by():
for name, path in entry.media_files.items(): for name, path in entry.media_files.items():
_log.info(u'Exporting {0} - {1}'.format( _log.info(u'Exporting {0} - {1}'.format(
entry.title, entry.title,

View File

@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from mediagoblin.gmg_commands import util as commands_util from mediagoblin.gmg_commands import util as commands_util
from mediagoblin.auth import lib as auth_lib from mediagoblin import auth
from mediagoblin import mg_globals from mediagoblin import mg_globals
def adduser_parser_setup(subparser): def adduser_parser_setup(subparser):
@ -40,9 +40,9 @@ def adduser(args):
db = mg_globals.database db = mg_globals.database
users_with_username = \ users_with_username = \
db.User.find({ db.User.query.filter_by(
'username': args.username.lower(), username=args.username.lower()
}).count() ).count()
if users_with_username: if users_with_username:
print u'Sorry, a user with that name already exists.' print u'Sorry, a user with that name already exists.'
@ -52,7 +52,7 @@ def adduser(args):
entry = db.User() entry = db.User()
entry.username = unicode(args.username.lower()) entry.username = unicode(args.username.lower())
entry.email = unicode(args.email) entry.email = unicode(args.email)
entry.pw_hash = auth_lib.bcrypt_gen_password_hash(args.password) entry.pw_hash = auth.gen_password_hash(args.password)
entry.status = u'active' entry.status = u'active'
entry.email_verified = True entry.email_verified = True
default_privileges = [ default_privileges = [
@ -78,7 +78,8 @@ def makeadmin(args):
db = mg_globals.database db = mg_globals.database
user = db.User.one({'username': unicode(args.username.lower())}) user = db.User.query.filter_by(
username=unicode(args.username.lower())).one()
if user: if user:
user.is_admin = True user.is_admin = True
user.all_privileges.append( user.all_privileges.append(
@ -105,9 +106,10 @@ def changepw(args):
db = mg_globals.database db = mg_globals.database
user = db.User.one({'username': unicode(args.username.lower())}) user = db.User.query.filter_by(
username=unicode(args.username.lower())).one()
if user: if user:
user.pw_hash = auth_lib.bcrypt_gen_password_hash(args.password) user.pw_hash = auth.gen_password_hash(args.password)
user.save() user.save()
print 'Password successfully changed' print 'Password successfully changed'
else: else:

View File

@ -15,15 +15,15 @@
# Elrond <elrond+mediagoblin.org@samba-tng.org>, 2011 # Elrond <elrond+mediagoblin.org@samba-tng.org>, 2011
# Art O. Pal <artopal@fastmail.fm>, 2011 # Art O. Pal <artopal@fastmail.fm>, 2011
# spaetz <sebastian@sspaeth.de>, 2012 # spaetz <sebastian@sspaeth.de>, 2012
# Vinzenz Vietzke <vietzke@b1-systems.de>, 2012 # Vinzenz Vietzke <vinz@vinzv.de>, 2012
# Vinzenz Vietzke <vietzke@b1-systems.de>, 2011 # Vinzenz Vietzke <vinz@vinzv.de>, 2011
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: GNU MediaGoblin\n" "Project-Id-Version: GNU MediaGoblin\n"
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
"POT-Creation-Date: 2013-05-27 13:54-0500\n" "POT-Creation-Date: 2013-05-27 13:54-0500\n"
"PO-Revision-Date: 2013-05-27 18:54+0000\n" "PO-Revision-Date: 2013-05-28 10:43+0000\n"
"Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Last-Translator: Elrond <elrond+mediagoblin.org@samba-tng.org>\n"
"Language-Team: German (http://www.transifex.com/projects/p/mediagoblin/language/de/)\n" "Language-Team: German (http://www.transifex.com/projects/p/mediagoblin/language/de/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -47,7 +47,7 @@ msgstr "E-Mail-Adresse"
#: mediagoblin/auth/forms.py:41 #: mediagoblin/auth/forms.py:41
msgid "Username or Email" msgid "Username or Email"
msgstr "" msgstr "Benutzername oder E-Mail-Adresse"
#: mediagoblin/auth/forms.py:52 #: mediagoblin/auth/forms.py:52
msgid "Username or email" msgid "Username or email"
@ -628,7 +628,7 @@ msgid ""
"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
" or\n" " or\n"
" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" " <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
msgstr "" msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Registriere dich auf dieser Seite</a> oder <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Installiere MediaGoblin auf deinem eigenen Server</a>"
#: mediagoblin/templates/mediagoblin/bits/logo.html:23 #: mediagoblin/templates/mediagoblin/bits/logo.html:23
#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 #: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
@ -784,7 +784,7 @@ msgstr "Bild für %(media_title)s"
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 #: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
msgid "PDF file" msgid "PDF file"
msgstr "" msgstr "PDF-Datei"
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 #: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
msgid "Toggle Rotate" msgid "Toggle Rotate"
@ -944,11 +944,11 @@ msgstr ""
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 #: mediagoblin/templates/mediagoblin/user_pages/media.html:150
msgid "Added" msgid "Added"
msgstr "" msgstr "Hinzugefügt"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 #: mediagoblin/templates/mediagoblin/user_pages/media.html:161
msgid "Created" msgid "Created"
msgstr "" msgstr "Originaldatum"
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
@ -1136,27 +1136,27 @@ msgstr "Tut uns Leid, aber unter der angegebenen Adresse gibt es keine Seite!</p
#: mediagoblin/tools/timesince.py:62 #: mediagoblin/tools/timesince.py:62
msgid "year" msgid "year"
msgstr "" msgstr "Jahr"
#: mediagoblin/tools/timesince.py:63 #: mediagoblin/tools/timesince.py:63
msgid "month" msgid "month"
msgstr "" msgstr "Monat"
#: mediagoblin/tools/timesince.py:64 #: mediagoblin/tools/timesince.py:64
msgid "week" msgid "week"
msgstr "" msgstr "Woche"
#: mediagoblin/tools/timesince.py:65 #: mediagoblin/tools/timesince.py:65
msgid "day" msgid "day"
msgstr "" msgstr "Tag"
#: mediagoblin/tools/timesince.py:66 #: mediagoblin/tools/timesince.py:66
msgid "hour" msgid "hour"
msgstr "" msgstr "Stunde"
#: mediagoblin/tools/timesince.py:67 #: mediagoblin/tools/timesince.py:67
msgid "minute" msgid "minute"
msgstr "" msgstr "Minute"
#: mediagoblin/user_pages/forms.py:23 #: mediagoblin/user_pages/forms.py:23
msgid "Comment" msgid "Comment"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2013-05-27 13:54-0500\n" "POT-Creation-Date: 2013-06-16 20:06-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,94 +17,94 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 0.9.6\n" "Generated-By: Babel 0.9.6\n"
#: mediagoblin/auth/forms.py:26 #: mediagoblin/auth/forms.py:25
msgid "Username" msgid "Username"
msgstr "" msgstr ""
#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 #: mediagoblin/auth/forms.py:29 mediagoblin/auth/forms.py:44
#: mediagoblin/tests/test_util.py:110 #: mediagoblin/tests/test_util.py:110
msgid "Password" msgid "Password"
msgstr "" msgstr ""
#: mediagoblin/auth/forms.py:34 #: mediagoblin/auth/forms.py:33
msgid "Email address" msgid "Email address"
msgstr "" msgstr ""
#: mediagoblin/auth/forms.py:41 #: mediagoblin/auth/forms.py:40
msgid "Username or Email" msgid "Username or Email"
msgstr "" msgstr ""
#: mediagoblin/auth/forms.py:52 #: mediagoblin/auth/forms.py:51
msgid "Username or email" msgid "Username or email"
msgstr "" msgstr ""
#: mediagoblin/auth/tools.py:31 #: mediagoblin/auth/tools.py:42
msgid "Invalid User name or email address." msgid "Invalid User name or email address."
msgstr "" msgstr ""
#: mediagoblin/auth/tools.py:32 #: mediagoblin/auth/tools.py:43
msgid "This field does not take email addresses." msgid "This field does not take email addresses."
msgstr "" msgstr ""
#: mediagoblin/auth/tools.py:33 #: mediagoblin/auth/tools.py:44
msgid "This field requires an email address." msgid "This field requires an email address."
msgstr "" msgstr ""
#: mediagoblin/auth/views.py:54 #: mediagoblin/auth/tools.py:109
msgid "Sorry, registration is disabled on this instance."
msgstr ""
#: mediagoblin/auth/views.py:68
msgid "Sorry, a user with that name already exists." msgid "Sorry, a user with that name already exists."
msgstr "" msgstr ""
#: mediagoblin/auth/views.py:72 #: mediagoblin/auth/tools.py:113
msgid "Sorry, a user with that email address already exists." msgid "Sorry, a user with that email address already exists."
msgstr "" msgstr ""
#: mediagoblin/auth/views.py:182 #: mediagoblin/auth/views.py:43
msgid "Sorry, registration is disabled on this instance."
msgstr ""
#: mediagoblin/auth/views.py:133
msgid "" msgid ""
"Your email address has been verified. You may now login, edit your " "Your email address has been verified. You may now login, edit your "
"profile, and submit images!" "profile, and submit images!"
msgstr "" msgstr ""
#: mediagoblin/auth/views.py:188 #: mediagoblin/auth/views.py:139
msgid "The verification key or user id is incorrect" msgid "The verification key or user id is incorrect"
msgstr "" msgstr ""
#: mediagoblin/auth/views.py:206 #: mediagoblin/auth/views.py:157
msgid "You must be logged in so we know who to send the email to!" msgid "You must be logged in so we know who to send the email to!"
msgstr "" msgstr ""
#: mediagoblin/auth/views.py:214 #: mediagoblin/auth/views.py:165
msgid "You've already verified your email address!" msgid "You've already verified your email address!"
msgstr "" msgstr ""
#: mediagoblin/auth/views.py:227 #: mediagoblin/auth/views.py:178
msgid "Resent your verification email." msgid "Resent your verification email."
msgstr "" msgstr ""
#: mediagoblin/auth/views.py:258 #: mediagoblin/auth/views.py:209
msgid "" msgid ""
"If that email address (case sensitive!) is registered an email has been " "If that email address (case sensitive!) is registered an email has been "
"sent with instructions on how to change your password." "sent with instructions on how to change your password."
msgstr "" msgstr ""
#: mediagoblin/auth/views.py:269 #: mediagoblin/auth/views.py:220
msgid "Couldn't find someone with that username." msgid "Couldn't find someone with that username."
msgstr "" msgstr ""
#: mediagoblin/auth/views.py:272 #: mediagoblin/auth/views.py:223
msgid "An email has been sent with instructions on how to change your password." msgid "An email has been sent with instructions on how to change your password."
msgstr "" msgstr ""
#: mediagoblin/auth/views.py:279 #: mediagoblin/auth/views.py:230
msgid "" msgid ""
"Could not send password recovery email as your username is inactive or " "Could not send password recovery email as your username is inactive or "
"your account's email address has not been verified." "your account's email address has not been verified."
msgstr "" msgstr ""
#: mediagoblin/auth/views.py:336 #: mediagoblin/auth/views.py:287
msgid "You can now log in using your new password." msgid "You can now log in using your new password."
msgstr "" msgstr ""
@ -634,13 +634,13 @@ msgid "Editing attachments for %(media_title)s"
msgstr "" msgstr ""
#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 #: mediagoblin/templates/mediagoblin/edit/attachments.html:44
#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 #: mediagoblin/templates/mediagoblin/user_pages/media.html:171
#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 #: mediagoblin/templates/mediagoblin/user_pages/media.html:187
msgid "Attachments" msgid "Attachments"
msgstr "" msgstr ""
#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 #: mediagoblin/templates/mediagoblin/edit/attachments.html:57
#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 #: mediagoblin/templates/mediagoblin/user_pages/media.html:193
msgid "Add attachment" msgid "Add attachment"
msgstr "" msgstr ""
@ -763,6 +763,17 @@ msgstr ""
msgid "WebM file (Vorbis codec)" msgid "WebM file (Vorbis codec)"
msgstr "" msgstr ""
#: mediagoblin/templates/mediagoblin/media_displays/image.html:36
msgid "Created"
msgstr ""
#: mediagoblin/templates/mediagoblin/media_displays/image.html:39
#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
#, python-format
msgid "%(formatted_time)s ago"
msgstr ""
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 #: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 #: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 #: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
@ -928,21 +939,10 @@ msgstr ""
msgid "Add this comment" msgid "Add this comment"
msgstr "" msgstr ""
#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
#, python-format
msgid "%(formatted_time)s ago"
msgstr ""
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 #: mediagoblin/templates/mediagoblin/user_pages/media.html:150
msgid "Added" msgid "Added"
msgstr "" msgstr ""
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
msgid "Created"
msgstr ""
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
#, python-format #, python-format

View File

@ -12,8 +12,8 @@ msgstr ""
"Project-Id-Version: GNU MediaGoblin\n" "Project-Id-Version: GNU MediaGoblin\n"
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
"POT-Creation-Date: 2013-05-27 13:54-0500\n" "POT-Creation-Date: 2013-05-27 13:54-0500\n"
"PO-Revision-Date: 2013-05-27 18:54+0000\n" "PO-Revision-Date: 2013-06-01 21:16+0000\n"
"Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Last-Translator: aleksejrs <deletesoftware@yandex.ru>\n"
"Language-Team: Esperanto (http://www.transifex.com/projects/p/mediagoblin/language/eo/)\n" "Language-Team: Esperanto (http://www.transifex.com/projects/p/mediagoblin/language/eo/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -37,7 +37,7 @@ msgstr "Retpoŝtadreso"
#: mediagoblin/auth/forms.py:41 #: mediagoblin/auth/forms.py:41
msgid "Username or Email" msgid "Username or Email"
msgstr "" msgstr "Uzantonomo aŭ retpoŝtadreso"
#: mediagoblin/auth/forms.py:52 #: mediagoblin/auth/forms.py:52
msgid "Username or email" msgid "Username or email"
@ -178,7 +178,7 @@ msgstr "Permesila prefero"
#: mediagoblin/edit/forms.py:69 #: mediagoblin/edit/forms.py:69
msgid "This will be your default license on upload forms." msgid "This will be your default license on upload forms."
msgstr "" msgstr "Tiu ĉi permesilo estos antaŭelektita en la alŝutformularoj."
#: mediagoblin/edit/forms.py:71 #: mediagoblin/edit/forms.py:71
msgid "Email me when others comment on my media" msgid "Email me when others comment on my media"
@ -264,7 +264,7 @@ msgstr "Malĝusta pasvorto"
#: mediagoblin/edit/views.py:363 #: mediagoblin/edit/views.py:363
msgid "Your password was changed successfully" msgid "Your password was changed successfully"
msgstr "" msgstr "Via pasvorto estas sukcese ŝanĝita"
#: mediagoblin/gmg_commands/assetlink.py:60 #: mediagoblin/gmg_commands/assetlink.py:60
msgid "Cannot link theme... no theme set\n" msgid "Cannot link theme... no theme set\n"
@ -665,11 +665,11 @@ msgstr "Konservi ŝanĝojn"
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 #: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
#, python-format #, python-format
msgid "Changing %(username)s's password" msgid "Changing %(username)s's password"
msgstr "" msgstr "Ŝanĝado de pasvorto de %(username)s"
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 #: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
msgid "Save" msgid "Save"
msgstr "" msgstr "Konservi"
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 #: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
#, python-format #, python-format
@ -700,7 +700,7 @@ msgstr "Ŝanĝado de kontagordoj de %(username)s"
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 #: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
msgid "Change your password." msgid "Change your password."
msgstr "" msgstr "Ŝanĝi la pasvorton"
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 #: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
msgid "Delete my account" msgid "Delete my account"
@ -774,7 +774,7 @@ msgstr "Bildo de «%(media_title)s»"
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 #: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
msgid "PDF file" msgid "PDF file"
msgstr "" msgstr "PDF-dosiero"
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 #: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
msgid "Toggle Rotate" msgid "Toggle Rotate"
@ -930,15 +930,15 @@ msgstr "Aldoni ĉi tiun komenton"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 #: mediagoblin/templates/mediagoblin/user_pages/media.html:164
#, python-format #, python-format
msgid "%(formatted_time)s ago" msgid "%(formatted_time)s ago"
msgstr "" msgstr "antaŭ %(formatted_time)s"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 #: mediagoblin/templates/mediagoblin/user_pages/media.html:150
msgid "Added" msgid "Added"
msgstr "" msgstr "Aldonita"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 #: mediagoblin/templates/mediagoblin/user_pages/media.html:161
msgid "Created" msgid "Created"
msgstr "" msgstr "Kreita"
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
@ -1126,27 +1126,27 @@ msgstr ""
#: mediagoblin/tools/timesince.py:62 #: mediagoblin/tools/timesince.py:62
msgid "year" msgid "year"
msgstr "" msgstr "jaro(j)"
#: mediagoblin/tools/timesince.py:63 #: mediagoblin/tools/timesince.py:63
msgid "month" msgid "month"
msgstr "" msgstr "monato(j)"
#: mediagoblin/tools/timesince.py:64 #: mediagoblin/tools/timesince.py:64
msgid "week" msgid "week"
msgstr "" msgstr "semajno(j)"
#: mediagoblin/tools/timesince.py:65 #: mediagoblin/tools/timesince.py:65
msgid "day" msgid "day"
msgstr "" msgstr "tago(j)"
#: mediagoblin/tools/timesince.py:66 #: mediagoblin/tools/timesince.py:66
msgid "hour" msgid "hour"
msgstr "" msgstr "horo(j)"
#: mediagoblin/tools/timesince.py:67 #: mediagoblin/tools/timesince.py:67
msgid "minute" msgid "minute"
msgstr "" msgstr "minuto(j)"
#: mediagoblin/user_pages/forms.py:23 #: mediagoblin/user_pages/forms.py:23
msgid "Comment" msgid "Comment"
@ -1185,7 +1185,7 @@ msgstr "komentis je via afiŝo"
#: mediagoblin/user_pages/views.py:169 #: mediagoblin/user_pages/views.py:169
msgid "Sorry, comments are disabled." msgid "Sorry, comments are disabled."
msgstr "" msgstr "Ve, komentado estas malebligita."
#: mediagoblin/user_pages/views.py:174 #: mediagoblin/user_pages/views.py:174
msgid "Oops, your comment was empty." msgid "Oops, your comment was empty."

View File

@ -20,8 +20,8 @@ msgstr ""
"Project-Id-Version: GNU MediaGoblin\n" "Project-Id-Version: GNU MediaGoblin\n"
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
"POT-Creation-Date: 2013-05-27 13:54-0500\n" "POT-Creation-Date: 2013-05-27 13:54-0500\n"
"PO-Revision-Date: 2013-05-27 18:54+0000\n" "PO-Revision-Date: 2013-06-02 21:23+0000\n"
"Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Last-Translator: larjona <larjona99@gmail.com>\n"
"Language-Team: Spanish (http://www.transifex.com/projects/p/mediagoblin/language/es/)\n" "Language-Team: Spanish (http://www.transifex.com/projects/p/mediagoblin/language/es/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -45,7 +45,7 @@ msgstr "Dirección de correo electrónico"
#: mediagoblin/auth/forms.py:41 #: mediagoblin/auth/forms.py:41
msgid "Username or Email" msgid "Username or Email"
msgstr "" msgstr "Nombre de usuario o correo electrónico"
#: mediagoblin/auth/forms.py:52 #: mediagoblin/auth/forms.py:52
msgid "Username or email" msgid "Username or email"
@ -272,7 +272,7 @@ msgstr "Contraseña incorrecta"
#: mediagoblin/edit/views.py:363 #: mediagoblin/edit/views.py:363
msgid "Your password was changed successfully" msgid "Your password was changed successfully"
msgstr "" msgstr "Se ha cambiado la contraseña correctamente"
#: mediagoblin/gmg_commands/assetlink.py:60 #: mediagoblin/gmg_commands/assetlink.py:60
msgid "Cannot link theme... no theme set\n" msgid "Cannot link theme... no theme set\n"
@ -289,17 +289,17 @@ msgstr "Sin embargo, se encontró un enlace simbólico de un directorio antiguo;
#: mediagoblin/gmg_commands/assetlink.py:112 #: mediagoblin/gmg_commands/assetlink.py:112
#, python-format #, python-format
msgid "Could not link \"%s\": %s exists and is not a symlink\n" msgid "Could not link \"%s\": %s exists and is not a symlink\n"
msgstr "" msgstr "No se pudo enlazar \"%s\": %s existe y no es un enlace simbólico\n"
#: mediagoblin/gmg_commands/assetlink.py:119 #: mediagoblin/gmg_commands/assetlink.py:119
#, python-format #, python-format
msgid "Skipping \"%s\"; already set up.\n" msgid "Skipping \"%s\"; already set up.\n"
msgstr "" msgstr "Omitiendo \"%s\"; ya está establecido.\n"
#: mediagoblin/gmg_commands/assetlink.py:124 #: mediagoblin/gmg_commands/assetlink.py:124
#, python-format #, python-format
msgid "Old link found for \"%s\"; removing.\n" msgid "Old link found for \"%s\"; removing.\n"
msgstr "" msgstr "Se encontró un enlace antiguo para \"%s\"; se eliminará.\n"
#: mediagoblin/meddleware/csrf.py:134 #: mediagoblin/meddleware/csrf.py:134
msgid "" msgid ""
@ -315,7 +315,7 @@ msgstr "Lo sentidos, No soportamos ese tipo de archivo :("
#: mediagoblin/media_types/pdf/processing.py:136 #: mediagoblin/media_types/pdf/processing.py:136
msgid "unoconv failing to run, check log file" msgid "unoconv failing to run, check log file"
msgstr "" msgstr "ha fallado la ejecución de unoconv, comprueba el fichero de registro (log)"
#: mediagoblin/media_types/video/processing.py:37 #: mediagoblin/media_types/video/processing.py:37
msgid "Video transcoding failed" msgid "Video transcoding failed"
@ -626,7 +626,7 @@ msgid ""
"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
" or\n" " or\n"
" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" " <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
msgstr "" msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Crear una cuenta en este sitio</a>\n o\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Instalar MediaGoblin en tu propio servidor</a>"
#: mediagoblin/templates/mediagoblin/bits/logo.html:23 #: mediagoblin/templates/mediagoblin/bits/logo.html:23
#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 #: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
@ -673,11 +673,11 @@ msgstr "Guardar cambios"
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 #: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
#, python-format #, python-format
msgid "Changing %(username)s's password" msgid "Changing %(username)s's password"
msgstr "" msgstr "Cambiando la contraseña de %(username)s"
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 #: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
msgid "Save" msgid "Save"
msgstr "" msgstr "Guardar"
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 #: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
#, python-format #, python-format
@ -708,7 +708,7 @@ msgstr "Cambio de %(username)s la configuración de la cuenta "
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 #: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
msgid "Change your password." msgid "Change your password."
msgstr "" msgstr "Cambiar tu contraseña."
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 #: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
msgid "Delete my account" msgid "Delete my account"
@ -782,7 +782,7 @@ msgstr "Imágenes para %(media_title)s"
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 #: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
msgid "PDF file" msgid "PDF file"
msgstr "" msgstr "Fichero PDF"
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 #: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
msgid "Toggle Rotate" msgid "Toggle Rotate"
@ -938,15 +938,15 @@ msgstr "Añade un comentario "
#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 #: mediagoblin/templates/mediagoblin/user_pages/media.html:164
#, python-format #, python-format
msgid "%(formatted_time)s ago" msgid "%(formatted_time)s ago"
msgstr "" msgstr "hace %(formatted_time)s"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 #: mediagoblin/templates/mediagoblin/user_pages/media.html:150
msgid "Added" msgid "Added"
msgstr "" msgstr "Agregado"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 #: mediagoblin/templates/mediagoblin/user_pages/media.html:161
msgid "Created" msgid "Created"
msgstr "" msgstr "Creado"
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
@ -1134,27 +1134,27 @@ msgstr "Parece que no hay ninguna página en esta dirección. ¡Lo siento!</p><p
#: mediagoblin/tools/timesince.py:62 #: mediagoblin/tools/timesince.py:62
msgid "year" msgid "year"
msgstr "" msgstr "año"
#: mediagoblin/tools/timesince.py:63 #: mediagoblin/tools/timesince.py:63
msgid "month" msgid "month"
msgstr "" msgstr "mes"
#: mediagoblin/tools/timesince.py:64 #: mediagoblin/tools/timesince.py:64
msgid "week" msgid "week"
msgstr "" msgstr "semana"
#: mediagoblin/tools/timesince.py:65 #: mediagoblin/tools/timesince.py:65
msgid "day" msgid "day"
msgstr "" msgstr "día"
#: mediagoblin/tools/timesince.py:66 #: mediagoblin/tools/timesince.py:66
msgid "hour" msgid "hour"
msgstr "" msgstr "hora"
#: mediagoblin/tools/timesince.py:67 #: mediagoblin/tools/timesince.py:67
msgid "minute" msgid "minute"
msgstr "" msgstr "minuto"
#: mediagoblin/user_pages/forms.py:23 #: mediagoblin/user_pages/forms.py:23
msgid "Comment" msgid "Comment"
@ -1193,7 +1193,7 @@ msgstr "comentó tu publicación"
#: mediagoblin/user_pages/views.py:169 #: mediagoblin/user_pages/views.py:169
msgid "Sorry, comments are disabled." msgid "Sorry, comments are disabled."
msgstr "" msgstr "Lo siento, los comentarios están desactivados."
#: mediagoblin/user_pages/views.py:174 #: mediagoblin/user_pages/views.py:174
msgid "Oops, your comment was empty." msgid "Oops, your comment was empty."

View File

@ -11,8 +11,8 @@ msgstr ""
"Project-Id-Version: GNU MediaGoblin\n" "Project-Id-Version: GNU MediaGoblin\n"
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
"POT-Creation-Date: 2013-05-27 13:54-0500\n" "POT-Creation-Date: 2013-05-27 13:54-0500\n"
"PO-Revision-Date: 2013-05-27 18:54+0000\n" "PO-Revision-Date: 2013-06-01 07:11+0000\n"
"Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Last-Translator: GenghisKhan <genghiskhan@gmx.ca>\n"
"Language-Team: Hebrew (http://www.transifex.com/projects/p/mediagoblin/language/he/)\n" "Language-Team: Hebrew (http://www.transifex.com/projects/p/mediagoblin/language/he/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -36,7 +36,7 @@ msgstr "כתובת דוא״ל"
#: mediagoblin/auth/forms.py:41 #: mediagoblin/auth/forms.py:41
msgid "Username or Email" msgid "Username or Email"
msgstr "" msgstr "שם משתמש או דוא״ל"
#: mediagoblin/auth/forms.py:52 #: mediagoblin/auth/forms.py:52
msgid "Username or email" msgid "Username or email"
@ -263,7 +263,7 @@ msgstr "סיסמה שגויה"
#: mediagoblin/edit/views.py:363 #: mediagoblin/edit/views.py:363
msgid "Your password was changed successfully" msgid "Your password was changed successfully"
msgstr "" msgstr "סיסמתך שונתה בהצלחה"
#: mediagoblin/gmg_commands/assetlink.py:60 #: mediagoblin/gmg_commands/assetlink.py:60
msgid "Cannot link theme... no theme set\n" msgid "Cannot link theme... no theme set\n"
@ -280,17 +280,17 @@ msgstr "בכל אופן, קישור מדור symlink נמצא; הוסר.\n"
#: mediagoblin/gmg_commands/assetlink.py:112 #: mediagoblin/gmg_commands/assetlink.py:112
#, python-format #, python-format
msgid "Could not link \"%s\": %s exists and is not a symlink\n" msgid "Could not link \"%s\": %s exists and is not a symlink\n"
msgstr "" msgstr "לא היתה אפשרות לקשר את \"%s\": %s קיים ואינו קישור סמלי (symlink)\n"
#: mediagoblin/gmg_commands/assetlink.py:119 #: mediagoblin/gmg_commands/assetlink.py:119
#, python-format #, python-format
msgid "Skipping \"%s\"; already set up.\n" msgid "Skipping \"%s\"; already set up.\n"
msgstr "" msgstr "מדלג על \"%s\"; כבר מוגדר.\n"
#: mediagoblin/gmg_commands/assetlink.py:124 #: mediagoblin/gmg_commands/assetlink.py:124
#, python-format #, python-format
msgid "Old link found for \"%s\"; removing.\n" msgid "Old link found for \"%s\"; removing.\n"
msgstr "" msgstr "קישור ישן נמצא עבור \"%s\"; מסיר כעת.\n"
#: mediagoblin/meddleware/csrf.py:134 #: mediagoblin/meddleware/csrf.py:134
msgid "" msgid ""
@ -306,7 +306,7 @@ msgstr "צר לי, אינני תומך בטיפוס קובץ זה :("
#: mediagoblin/media_types/pdf/processing.py:136 #: mediagoblin/media_types/pdf/processing.py:136
msgid "unoconv failing to run, check log file" msgid "unoconv failing to run, check log file"
msgstr "" msgstr "unoconv נכשל לפעול, בדוק קובץ יומן"
#: mediagoblin/media_types/video/processing.py:37 #: mediagoblin/media_types/video/processing.py:37
msgid "Video transcoding failed" msgid "Video transcoding failed"
@ -617,7 +617,7 @@ msgid ""
"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
" or\n" " or\n"
" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" " <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
msgstr "" msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">צור חשבון באתר זה</a>\n או\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">התקן את MediaGoblin על שרתך</a>"
#: mediagoblin/templates/mediagoblin/bits/logo.html:23 #: mediagoblin/templates/mediagoblin/bits/logo.html:23
#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 #: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
@ -664,11 +664,11 @@ msgstr "שמור שינויים"
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 #: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
#, python-format #, python-format
msgid "Changing %(username)s's password" msgid "Changing %(username)s's password"
msgstr "" msgstr "משנה כעת את הסיסמה של %(username)s'"
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 #: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
msgid "Save" msgid "Save"
msgstr "" msgstr "שמור"
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 #: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
#, python-format #, python-format
@ -699,7 +699,7 @@ msgstr "שינוי הגדרות חשבון עבור %(username)s"
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 #: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
msgid "Change your password." msgid "Change your password."
msgstr "" msgstr "שנה את סיסמתך."
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 #: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
msgid "Delete my account" msgid "Delete my account"
@ -773,7 +773,7 @@ msgstr "תמונה עבור %(media_title)s"
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 #: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
msgid "PDF file" msgid "PDF file"
msgstr "" msgstr "קובץ PDF"
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 #: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
msgid "Toggle Rotate" msgid "Toggle Rotate"
@ -929,15 +929,15 @@ msgstr "הוסף את תגובה זו"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 #: mediagoblin/templates/mediagoblin/user_pages/media.html:164
#, python-format #, python-format
msgid "%(formatted_time)s ago" msgid "%(formatted_time)s ago"
msgstr "" msgstr "מלפני %(formatted_time)s"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 #: mediagoblin/templates/mediagoblin/user_pages/media.html:150
msgid "Added" msgid "Added"
msgstr "" msgstr "התווסף"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 #: mediagoblin/templates/mediagoblin/user_pages/media.html:161
msgid "Created" msgid "Created"
msgstr "" msgstr "נוצר"
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
@ -1125,27 +1125,27 @@ msgstr "לא נראה שקיים עמוד בכתובת זו. צר לי!</p><p>א
#: mediagoblin/tools/timesince.py:62 #: mediagoblin/tools/timesince.py:62
msgid "year" msgid "year"
msgstr "" msgstr "שנה"
#: mediagoblin/tools/timesince.py:63 #: mediagoblin/tools/timesince.py:63
msgid "month" msgid "month"
msgstr "" msgstr "חודש"
#: mediagoblin/tools/timesince.py:64 #: mediagoblin/tools/timesince.py:64
msgid "week" msgid "week"
msgstr "" msgstr "שבוע"
#: mediagoblin/tools/timesince.py:65 #: mediagoblin/tools/timesince.py:65
msgid "day" msgid "day"
msgstr "" msgstr "יום"
#: mediagoblin/tools/timesince.py:66 #: mediagoblin/tools/timesince.py:66
msgid "hour" msgid "hour"
msgstr "" msgstr "שעה"
#: mediagoblin/tools/timesince.py:67 #: mediagoblin/tools/timesince.py:67
msgid "minute" msgid "minute"
msgstr "" msgstr "דקה"
#: mediagoblin/user_pages/forms.py:23 #: mediagoblin/user_pages/forms.py:23
msgid "Comment" msgid "Comment"
@ -1184,7 +1184,7 @@ msgstr "הגיב/ה על פרסומך"
#: mediagoblin/user_pages/views.py:169 #: mediagoblin/user_pages/views.py:169
msgid "Sorry, comments are disabled." msgid "Sorry, comments are disabled."
msgstr "" msgstr "מצטערים, תגובות מנוטרלות."
#: mediagoblin/user_pages/views.py:174 #: mediagoblin/user_pages/views.py:174
msgid "Oops, your comment was empty." msgid "Oops, your comment was empty."

View File

@ -10,8 +10,8 @@ msgstr ""
"Project-Id-Version: GNU MediaGoblin\n" "Project-Id-Version: GNU MediaGoblin\n"
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
"POT-Creation-Date: 2013-05-27 13:54-0500\n" "POT-Creation-Date: 2013-05-27 13:54-0500\n"
"PO-Revision-Date: 2013-05-27 18:54+0000\n" "PO-Revision-Date: 2013-06-05 22:51+0000\n"
"Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Last-Translator: tryggvib <tryggvib@fsfi.is>\n"
"Language-Team: Icelandic (Iceland) (http://www.transifex.com/projects/p/mediagoblin/language/is_IS/)\n" "Language-Team: Icelandic (Iceland) (http://www.transifex.com/projects/p/mediagoblin/language/is_IS/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -35,7 +35,7 @@ msgstr "Netfang"
#: mediagoblin/auth/forms.py:41 #: mediagoblin/auth/forms.py:41
msgid "Username or Email" msgid "Username or Email"
msgstr "" msgstr "Notandanafn eða tölvupóstur"
#: mediagoblin/auth/forms.py:52 #: mediagoblin/auth/forms.py:52
msgid "Username or email" msgid "Username or email"
@ -262,7 +262,7 @@ msgstr "Vitlaust lykilorð"
#: mediagoblin/edit/views.py:363 #: mediagoblin/edit/views.py:363
msgid "Your password was changed successfully" msgid "Your password was changed successfully"
msgstr "" msgstr "Það tókst að breyta lykilorðinu þínu"
#: mediagoblin/gmg_commands/assetlink.py:60 #: mediagoblin/gmg_commands/assetlink.py:60
msgid "Cannot link theme... no theme set\n" msgid "Cannot link theme... no theme set\n"
@ -279,17 +279,17 @@ msgstr "Fann samt gamlan táknrænan tengil á möppu; fjarlægður.\n"
#: mediagoblin/gmg_commands/assetlink.py:112 #: mediagoblin/gmg_commands/assetlink.py:112
#, python-format #, python-format
msgid "Could not link \"%s\": %s exists and is not a symlink\n" msgid "Could not link \"%s\": %s exists and is not a symlink\n"
msgstr "" msgstr "Gat ekki tengt \"%s\": %s er til og er ekki sýndartengill\n"
#: mediagoblin/gmg_commands/assetlink.py:119 #: mediagoblin/gmg_commands/assetlink.py:119
#, python-format #, python-format
msgid "Skipping \"%s\"; already set up.\n" msgid "Skipping \"%s\"; already set up.\n"
msgstr "" msgstr "Hoppa yfir \"%s\"; hefur nú þegar verið sett upp.\n"
#: mediagoblin/gmg_commands/assetlink.py:124 #: mediagoblin/gmg_commands/assetlink.py:124
#, python-format #, python-format
msgid "Old link found for \"%s\"; removing.\n" msgid "Old link found for \"%s\"; removing.\n"
msgstr "" msgstr "Gamall tengill fannst fyrir \"%s\"; fjarlægi.\n"
#: mediagoblin/meddleware/csrf.py:134 #: mediagoblin/meddleware/csrf.py:134
msgid "" msgid ""
@ -305,7 +305,7 @@ msgstr "Ég styð því miður ekki þessa gerð af skrám :("
#: mediagoblin/media_types/pdf/processing.py:136 #: mediagoblin/media_types/pdf/processing.py:136
msgid "unoconv failing to run, check log file" msgid "unoconv failing to run, check log file"
msgstr "" msgstr "tekst ekki að keyra unoconv, athugaðu annálsskrá"
#: mediagoblin/media_types/video/processing.py:37 #: mediagoblin/media_types/video/processing.py:37
msgid "Video transcoding failed" msgid "Video transcoding failed"
@ -616,7 +616,7 @@ msgid ""
"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
" or\n" " or\n"
" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" " <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
msgstr "" msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Búa til aðgang á þessari síðu</a>\neða\n<a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Settu upp þinn eigin margmiðlunarþjón</a>"
#: mediagoblin/templates/mediagoblin/bits/logo.html:23 #: mediagoblin/templates/mediagoblin/bits/logo.html:23
#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 #: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
@ -663,11 +663,11 @@ msgstr "Vista breytingar"
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 #: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
#, python-format #, python-format
msgid "Changing %(username)s's password" msgid "Changing %(username)s's password"
msgstr "" msgstr "Breyti lykilorði fyrir notandann: %(username)s"
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 #: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
msgid "Save" msgid "Save"
msgstr "" msgstr "Vista"
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 #: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
#, python-format #, python-format
@ -698,7 +698,7 @@ msgstr "Breyti notandaaðgangsstillingum fyrir: %(username)s"
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 #: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
msgid "Change your password." msgid "Change your password."
msgstr "" msgstr "Breyta lykilorðinu þínu."
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 #: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
msgid "Delete my account" msgid "Delete my account"
@ -772,7 +772,7 @@ msgstr "Mynd fyrir %(media_title)s"
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 #: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
msgid "PDF file" msgid "PDF file"
msgstr "" msgstr "PDF skrá"
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 #: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
msgid "Toggle Rotate" msgid "Toggle Rotate"
@ -928,15 +928,15 @@ msgstr "Senda inn þessa athugasemd"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 #: mediagoblin/templates/mediagoblin/user_pages/media.html:164
#, python-format #, python-format
msgid "%(formatted_time)s ago" msgid "%(formatted_time)s ago"
msgstr "" msgstr "Fyrir %(formatted_time)s"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 #: mediagoblin/templates/mediagoblin/user_pages/media.html:150
msgid "Added" msgid "Added"
msgstr "" msgstr "Bætt við"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 #: mediagoblin/templates/mediagoblin/user_pages/media.html:161
msgid "Created" msgid "Created"
msgstr "" msgstr "Skapað"
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
@ -1124,27 +1124,27 @@ msgstr "Því miður! Það virðist ekki vera nein síða á þessari vefslóð
#: mediagoblin/tools/timesince.py:62 #: mediagoblin/tools/timesince.py:62
msgid "year" msgid "year"
msgstr "" msgstr "ár"
#: mediagoblin/tools/timesince.py:63 #: mediagoblin/tools/timesince.py:63
msgid "month" msgid "month"
msgstr "" msgstr "mánuður"
#: mediagoblin/tools/timesince.py:64 #: mediagoblin/tools/timesince.py:64
msgid "week" msgid "week"
msgstr "" msgstr "vika"
#: mediagoblin/tools/timesince.py:65 #: mediagoblin/tools/timesince.py:65
msgid "day" msgid "day"
msgstr "" msgstr "dagur"
#: mediagoblin/tools/timesince.py:66 #: mediagoblin/tools/timesince.py:66
msgid "hour" msgid "hour"
msgstr "" msgstr "klukkustund"
#: mediagoblin/tools/timesince.py:67 #: mediagoblin/tools/timesince.py:67
msgid "minute" msgid "minute"
msgstr "" msgstr "mínúta"
#: mediagoblin/user_pages/forms.py:23 #: mediagoblin/user_pages/forms.py:23
msgid "Comment" msgid "Comment"
@ -1183,7 +1183,7 @@ msgstr "skrifaði athugasemd við færsluna þína"
#: mediagoblin/user_pages/views.py:169 #: mediagoblin/user_pages/views.py:169
msgid "Sorry, comments are disabled." msgid "Sorry, comments are disabled."
msgstr "" msgstr "Því miður, athugasemdir eru óvirkar."
#: mediagoblin/user_pages/views.py:174 #: mediagoblin/user_pages/views.py:174
msgid "Oops, your comment was empty." msgid "Oops, your comment was empty."

View File

@ -10,8 +10,8 @@ msgstr ""
"Project-Id-Version: GNU MediaGoblin\n" "Project-Id-Version: GNU MediaGoblin\n"
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
"POT-Creation-Date: 2013-05-27 13:54-0500\n" "POT-Creation-Date: 2013-05-27 13:54-0500\n"
"PO-Revision-Date: 2013-05-27 18:54+0000\n" "PO-Revision-Date: 2013-05-31 15:40+0000\n"
"Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Last-Translator: velmont <odin.omdal@gmail.com>\n"
"Language-Team: Norwegian Nynorsk (Norway) (http://www.transifex.com/projects/p/mediagoblin/language/nn_NO/)\n" "Language-Team: Norwegian Nynorsk (Norway) (http://www.transifex.com/projects/p/mediagoblin/language/nn_NO/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -35,7 +35,7 @@ msgstr "Epost"
#: mediagoblin/auth/forms.py:41 #: mediagoblin/auth/forms.py:41
msgid "Username or Email" msgid "Username or Email"
msgstr "" msgstr "Brukarnamn eller epost"
#: mediagoblin/auth/forms.py:52 #: mediagoblin/auth/forms.py:52
msgid "Username or email" msgid "Username or email"
@ -262,7 +262,7 @@ msgstr "Feil passord"
#: mediagoblin/edit/views.py:363 #: mediagoblin/edit/views.py:363
msgid "Your password was changed successfully" msgid "Your password was changed successfully"
msgstr "" msgstr "Endra passord"
#: mediagoblin/gmg_commands/assetlink.py:60 #: mediagoblin/gmg_commands/assetlink.py:60
msgid "Cannot link theme... no theme set\n" msgid "Cannot link theme... no theme set\n"
@ -279,17 +279,17 @@ msgstr "However, old link directory symlink found; removed.\n"
#: mediagoblin/gmg_commands/assetlink.py:112 #: mediagoblin/gmg_commands/assetlink.py:112
#, python-format #, python-format
msgid "Could not link \"%s\": %s exists and is not a symlink\n" msgid "Could not link \"%s\": %s exists and is not a symlink\n"
msgstr "" msgstr "Kunne ikkje lenkja «%s»: %s eksisterer og er ikkje ei symlenkje\n"
#: mediagoblin/gmg_commands/assetlink.py:119 #: mediagoblin/gmg_commands/assetlink.py:119
#, python-format #, python-format
msgid "Skipping \"%s\"; already set up.\n" msgid "Skipping \"%s\"; already set up.\n"
msgstr "" msgstr "Hopper over «%s»: allereie satt opp.\n"
#: mediagoblin/gmg_commands/assetlink.py:124 #: mediagoblin/gmg_commands/assetlink.py:124
#, python-format #, python-format
msgid "Old link found for \"%s\"; removing.\n" msgid "Old link found for \"%s\"; removing.\n"
msgstr "" msgstr "Gamal lenkje funnen for «%s»; fjernar.\n"
#: mediagoblin/meddleware/csrf.py:134 #: mediagoblin/meddleware/csrf.py:134
msgid "" msgid ""
@ -305,7 +305,7 @@ msgstr "Orsak, stør ikkje den filtypen :("
#: mediagoblin/media_types/pdf/processing.py:136 #: mediagoblin/media_types/pdf/processing.py:136
msgid "unoconv failing to run, check log file" msgid "unoconv failing to run, check log file"
msgstr "" msgstr "klarte ikkje køyra unoconv, sjekk logg-fil"
#: mediagoblin/media_types/video/processing.py:37 #: mediagoblin/media_types/video/processing.py:37
msgid "Video transcoding failed" msgid "Video transcoding failed"
@ -616,7 +616,7 @@ msgid ""
"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
" or\n" " or\n"
" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" " <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
msgstr "" msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Opprett ein konto på denne sida</a>\n eller\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set opp din eigen MediaGoblin-server</a>"
#: mediagoblin/templates/mediagoblin/bits/logo.html:23 #: mediagoblin/templates/mediagoblin/bits/logo.html:23
#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 #: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
@ -663,11 +663,11 @@ msgstr "Lagra"
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 #: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
#, python-format #, python-format
msgid "Changing %(username)s's password" msgid "Changing %(username)s's password"
msgstr "" msgstr "Endrar passordet til %(username)s"
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 #: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
msgid "Save" msgid "Save"
msgstr "" msgstr "Lagra"
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 #: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
#, python-format #, python-format
@ -698,7 +698,7 @@ msgstr "Endrar kontoinnstellingane til %(username)s"
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 #: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
msgid "Change your password." msgid "Change your password."
msgstr "" msgstr "Endra passordet ditt."
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 #: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
msgid "Delete my account" msgid "Delete my account"
@ -772,7 +772,7 @@ msgstr "Bilete for %(media_title)s"
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 #: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
msgid "PDF file" msgid "PDF file"
msgstr "" msgstr "PDF-fil"
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 #: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
msgid "Toggle Rotate" msgid "Toggle Rotate"
@ -928,15 +928,15 @@ msgstr "Legg til dette innspelet"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 #: mediagoblin/templates/mediagoblin/user_pages/media.html:164
#, python-format #, python-format
msgid "%(formatted_time)s ago" msgid "%(formatted_time)s ago"
msgstr "" msgstr "%(formatted_time)s sidan"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 #: mediagoblin/templates/mediagoblin/user_pages/media.html:150
msgid "Added" msgid "Added"
msgstr "" msgstr "Lagt til"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 #: mediagoblin/templates/mediagoblin/user_pages/media.html:161
msgid "Created" msgid "Created"
msgstr "" msgstr "Oppretta"
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
@ -1124,27 +1124,27 @@ msgstr "Ser ikkje ut til å finnast noko her. Orsak.</p>\n<p>Dersom du er sikker
#: mediagoblin/tools/timesince.py:62 #: mediagoblin/tools/timesince.py:62
msgid "year" msgid "year"
msgstr "" msgstr "år"
#: mediagoblin/tools/timesince.py:63 #: mediagoblin/tools/timesince.py:63
msgid "month" msgid "month"
msgstr "" msgstr "månad"
#: mediagoblin/tools/timesince.py:64 #: mediagoblin/tools/timesince.py:64
msgid "week" msgid "week"
msgstr "" msgstr "veke"
#: mediagoblin/tools/timesince.py:65 #: mediagoblin/tools/timesince.py:65
msgid "day" msgid "day"
msgstr "" msgstr "dag"
#: mediagoblin/tools/timesince.py:66 #: mediagoblin/tools/timesince.py:66
msgid "hour" msgid "hour"
msgstr "" msgstr "time"
#: mediagoblin/tools/timesince.py:67 #: mediagoblin/tools/timesince.py:67
msgid "minute" msgid "minute"
msgstr "" msgstr "minutt"
#: mediagoblin/user_pages/forms.py:23 #: mediagoblin/user_pages/forms.py:23
msgid "Comment" msgid "Comment"
@ -1183,7 +1183,7 @@ msgstr "kom med innspel på innlegget ditt"
#: mediagoblin/user_pages/views.py:169 #: mediagoblin/user_pages/views.py:169
msgid "Sorry, comments are disabled." msgid "Sorry, comments are disabled."
msgstr "" msgstr "Innspel er avslege"
#: mediagoblin/user_pages/views.py:174 #: mediagoblin/user_pages/views.py:174
msgid "Oops, your comment was empty." msgid "Oops, your comment was empty."

View File

@ -10,8 +10,8 @@ msgstr ""
"Project-Id-Version: GNU MediaGoblin\n" "Project-Id-Version: GNU MediaGoblin\n"
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
"POT-Creation-Date: 2013-05-27 13:54-0500\n" "POT-Creation-Date: 2013-05-27 13:54-0500\n"
"PO-Revision-Date: 2013-05-27 18:54+0000\n" "PO-Revision-Date: 2013-05-28 13:51+0000\n"
"Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Last-Translator: Sergiusz Pawlowicz <transifex@pawlowicz.name>\n"
"Language-Team: Polish (http://www.transifex.com/projects/p/mediagoblin/language/pl/)\n" "Language-Team: Polish (http://www.transifex.com/projects/p/mediagoblin/language/pl/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -35,7 +35,7 @@ msgstr "Adres e-mail"
#: mediagoblin/auth/forms.py:41 #: mediagoblin/auth/forms.py:41
msgid "Username or Email" msgid "Username or Email"
msgstr "" msgstr "Nazwa konta lub adres poczty elektronicznej"
#: mediagoblin/auth/forms.py:52 #: mediagoblin/auth/forms.py:52
msgid "Username or email" msgid "Username or email"
@ -262,7 +262,7 @@ msgstr "Nieprawidłowe hasło"
#: mediagoblin/edit/views.py:363 #: mediagoblin/edit/views.py:363
msgid "Your password was changed successfully" msgid "Your password was changed successfully"
msgstr "" msgstr "Twoje hasło zostało zmienione"
#: mediagoblin/gmg_commands/assetlink.py:60 #: mediagoblin/gmg_commands/assetlink.py:60
msgid "Cannot link theme... no theme set\n" msgid "Cannot link theme... no theme set\n"
@ -279,17 +279,17 @@ msgstr "Znaleziono stary odnośnik symboliczny do katalogu; usunięto.\n"
#: mediagoblin/gmg_commands/assetlink.py:112 #: mediagoblin/gmg_commands/assetlink.py:112
#, python-format #, python-format
msgid "Could not link \"%s\": %s exists and is not a symlink\n" msgid "Could not link \"%s\": %s exists and is not a symlink\n"
msgstr "" msgstr "Nie mogę zrobić odnośnika \"%s\": %s istnieje i nie jest odnośnikiem\n"
#: mediagoblin/gmg_commands/assetlink.py:119 #: mediagoblin/gmg_commands/assetlink.py:119
#, python-format #, python-format
msgid "Skipping \"%s\"; already set up.\n" msgid "Skipping \"%s\"; already set up.\n"
msgstr "" msgstr "Opuszczam \"%s\"; już jest gotowe.\n"
#: mediagoblin/gmg_commands/assetlink.py:124 #: mediagoblin/gmg_commands/assetlink.py:124
#, python-format #, python-format
msgid "Old link found for \"%s\"; removing.\n" msgid "Old link found for \"%s\"; removing.\n"
msgstr "" msgstr "Znaleziono stary odnośnik dla \"%s\"; usuwam.\n"
#: mediagoblin/meddleware/csrf.py:134 #: mediagoblin/meddleware/csrf.py:134
msgid "" msgid ""
@ -305,7 +305,7 @@ msgstr "NIestety, nie obsługujemy tego typu plików :-("
#: mediagoblin/media_types/pdf/processing.py:136 #: mediagoblin/media_types/pdf/processing.py:136
msgid "unoconv failing to run, check log file" msgid "unoconv failing to run, check log file"
msgstr "" msgstr "nie dało się uruchomić unoconv, sprawdź log"
#: mediagoblin/media_types/video/processing.py:37 #: mediagoblin/media_types/video/processing.py:37
msgid "Video transcoding failed" msgid "Video transcoding failed"
@ -616,7 +616,7 @@ msgid ""
"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
" or\n" " or\n"
" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" " <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
msgstr "" msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Załóż konto na tym serwerze</a>\n albo\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Uruchom MediaGoblin na swoim własnym serwerze</a>"
#: mediagoblin/templates/mediagoblin/bits/logo.html:23 #: mediagoblin/templates/mediagoblin/bits/logo.html:23
#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 #: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
@ -663,11 +663,11 @@ msgstr "Zapisz zmiany"
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 #: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
#, python-format #, python-format
msgid "Changing %(username)s's password" msgid "Changing %(username)s's password"
msgstr "" msgstr "Zmieniam hasło użytkownika %(username)s"
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 #: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
msgid "Save" msgid "Save"
msgstr "" msgstr "Zachowaj"
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 #: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
#, python-format #, python-format
@ -698,7 +698,7 @@ msgstr "Zmiana ustawień konta %(username)s"
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 #: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
msgid "Change your password." msgid "Change your password."
msgstr "" msgstr "Zmień swoje hasło."
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 #: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
msgid "Delete my account" msgid "Delete my account"
@ -772,7 +772,7 @@ msgstr "Grafika dla %(media_title)s"
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 #: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
msgid "PDF file" msgid "PDF file"
msgstr "" msgstr "Plik PDF"
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 #: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
msgid "Toggle Rotate" msgid "Toggle Rotate"
@ -928,15 +928,15 @@ msgstr "Dodaj komentarz"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 #: mediagoblin/templates/mediagoblin/user_pages/media.html:164
#, python-format #, python-format
msgid "%(formatted_time)s ago" msgid "%(formatted_time)s ago"
msgstr "" msgstr "%(formatted_time)s temu"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 #: mediagoblin/templates/mediagoblin/user_pages/media.html:150
msgid "Added" msgid "Added"
msgstr "" msgstr "Dodano"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 #: mediagoblin/templates/mediagoblin/user_pages/media.html:161
msgid "Created" msgid "Created"
msgstr "" msgstr "Utworzono"
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
@ -1124,27 +1124,27 @@ msgstr "Wygląda na to, że nic tutaj nie ma!</p><p>Jeśli jesteś pewny, że ad
#: mediagoblin/tools/timesince.py:62 #: mediagoblin/tools/timesince.py:62
msgid "year" msgid "year"
msgstr "" msgstr "rok"
#: mediagoblin/tools/timesince.py:63 #: mediagoblin/tools/timesince.py:63
msgid "month" msgid "month"
msgstr "" msgstr "miesiąc"
#: mediagoblin/tools/timesince.py:64 #: mediagoblin/tools/timesince.py:64
msgid "week" msgid "week"
msgstr "" msgstr "tydzień"
#: mediagoblin/tools/timesince.py:65 #: mediagoblin/tools/timesince.py:65
msgid "day" msgid "day"
msgstr "" msgstr "dzień"
#: mediagoblin/tools/timesince.py:66 #: mediagoblin/tools/timesince.py:66
msgid "hour" msgid "hour"
msgstr "" msgstr "godzina"
#: mediagoblin/tools/timesince.py:67 #: mediagoblin/tools/timesince.py:67
msgid "minute" msgid "minute"
msgstr "" msgstr "minuta"
#: mediagoblin/user_pages/forms.py:23 #: mediagoblin/user_pages/forms.py:23
msgid "Comment" msgid "Comment"
@ -1183,7 +1183,7 @@ msgstr "komentarze do twojego wpisu"
#: mediagoblin/user_pages/views.py:169 #: mediagoblin/user_pages/views.py:169
msgid "Sorry, comments are disabled." msgid "Sorry, comments are disabled."
msgstr "" msgstr "Komentowanie jest wyłączone."
#: mediagoblin/user_pages/views.py:174 #: mediagoblin/user_pages/views.py:174
msgid "Oops, your comment was empty." msgid "Oops, your comment was empty."

View File

@ -10,8 +10,8 @@ msgstr ""
"Project-Id-Version: GNU MediaGoblin\n" "Project-Id-Version: GNU MediaGoblin\n"
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
"POT-Creation-Date: 2013-05-27 13:54-0500\n" "POT-Creation-Date: 2013-05-27 13:54-0500\n"
"PO-Revision-Date: 2013-05-27 18:54+0000\n" "PO-Revision-Date: 2013-05-27 20:40+0000\n"
"Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Last-Translator: George Pop <gapop@hotmail.com>\n"
"Language-Team: Romanian (http://www.transifex.com/projects/p/mediagoblin/language/ro/)\n" "Language-Team: Romanian (http://www.transifex.com/projects/p/mediagoblin/language/ro/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -35,7 +35,7 @@ msgstr "Adresa de e-mail"
#: mediagoblin/auth/forms.py:41 #: mediagoblin/auth/forms.py:41
msgid "Username or Email" msgid "Username or Email"
msgstr "" msgstr "Numele de utilizator sau adresa de e-mail"
#: mediagoblin/auth/forms.py:52 #: mediagoblin/auth/forms.py:52
msgid "Username or email" msgid "Username or email"
@ -262,7 +262,7 @@ msgstr "Parolă incorectă"
#: mediagoblin/edit/views.py:363 #: mediagoblin/edit/views.py:363
msgid "Your password was changed successfully" msgid "Your password was changed successfully"
msgstr "" msgstr "Parola a fost schimbată cu succes"
#: mediagoblin/gmg_commands/assetlink.py:60 #: mediagoblin/gmg_commands/assetlink.py:60
msgid "Cannot link theme... no theme set\n" msgid "Cannot link theme... no theme set\n"
@ -279,17 +279,17 @@ msgstr "A fost însă găsit un symlink către vechiul folder; s-a șters.\n"
#: mediagoblin/gmg_commands/assetlink.py:112 #: mediagoblin/gmg_commands/assetlink.py:112
#, python-format #, python-format
msgid "Could not link \"%s\": %s exists and is not a symlink\n" msgid "Could not link \"%s\": %s exists and is not a symlink\n"
msgstr "" msgstr "Nu s-a putut crea link pentru \"%s\": %s există deja și nu este symlink\n"
#: mediagoblin/gmg_commands/assetlink.py:119 #: mediagoblin/gmg_commands/assetlink.py:119
#, python-format #, python-format
msgid "Skipping \"%s\"; already set up.\n" msgid "Skipping \"%s\"; already set up.\n"
msgstr "" msgstr "S-a omis \"%s\"; configurat deja.\n"
#: mediagoblin/gmg_commands/assetlink.py:124 #: mediagoblin/gmg_commands/assetlink.py:124
#, python-format #, python-format
msgid "Old link found for \"%s\"; removing.\n" msgid "Old link found for \"%s\"; removing.\n"
msgstr "" msgstr "Există deja un link pentru \"%s\"; va fi șters.\n"
#: mediagoblin/meddleware/csrf.py:134 #: mediagoblin/meddleware/csrf.py:134
msgid "" msgid ""
@ -305,7 +305,7 @@ msgstr "Scuze, nu recunosc acest tip de fișier :("
#: mediagoblin/media_types/pdf/processing.py:136 #: mediagoblin/media_types/pdf/processing.py:136
msgid "unoconv failing to run, check log file" msgid "unoconv failing to run, check log file"
msgstr "" msgstr "unoconv nu poate fi executat; verificați log-ul"
#: mediagoblin/media_types/video/processing.py:37 #: mediagoblin/media_types/video/processing.py:37
msgid "Video transcoding failed" msgid "Video transcoding failed"
@ -616,7 +616,7 @@ msgid ""
"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
" or\n" " or\n"
" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" " <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
msgstr "" msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Creați un cont pe acest site</a>\n sau\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Instalați MediaGoblin pe serverul dvs.</a>"
#: mediagoblin/templates/mediagoblin/bits/logo.html:23 #: mediagoblin/templates/mediagoblin/bits/logo.html:23
#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 #: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
@ -663,11 +663,11 @@ msgstr "Salvează modificările"
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 #: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
#, python-format #, python-format
msgid "Changing %(username)s's password" msgid "Changing %(username)s's password"
msgstr "" msgstr "Se modifică parola pentru %(username)s"
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 #: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
msgid "Save" msgid "Save"
msgstr "" msgstr "Salvează"
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 #: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
#, python-format #, python-format
@ -698,7 +698,7 @@ msgstr "Se modifică setările contului pentru userul %(username)s"
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 #: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
msgid "Change your password." msgid "Change your password."
msgstr "" msgstr "Modifică parolă."
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 #: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
msgid "Delete my account" msgid "Delete my account"
@ -772,7 +772,7 @@ msgstr "Imagine pentru %(media_title)s"
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 #: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
msgid "PDF file" msgid "PDF file"
msgstr "" msgstr "Fișier PDF"
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 #: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
msgid "Toggle Rotate" msgid "Toggle Rotate"
@ -928,15 +928,15 @@ msgstr "Trimite acest comentariu"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 #: mediagoblin/templates/mediagoblin/user_pages/media.html:164
#, python-format #, python-format
msgid "%(formatted_time)s ago" msgid "%(formatted_time)s ago"
msgstr "" msgstr "în urmă cu %(formatted_time)s"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 #: mediagoblin/templates/mediagoblin/user_pages/media.html:150
msgid "Added" msgid "Added"
msgstr "" msgstr "Adăugat"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 #: mediagoblin/templates/mediagoblin/user_pages/media.html:161
msgid "Created" msgid "Created"
msgstr "" msgstr "Creat"
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
@ -1124,27 +1124,27 @@ msgstr "Nu există nicio pagină la această adresă.</p><p>Dacă sunteți sigur
#: mediagoblin/tools/timesince.py:62 #: mediagoblin/tools/timesince.py:62
msgid "year" msgid "year"
msgstr "" msgstr "anul"
#: mediagoblin/tools/timesince.py:63 #: mediagoblin/tools/timesince.py:63
msgid "month" msgid "month"
msgstr "" msgstr "luna"
#: mediagoblin/tools/timesince.py:64 #: mediagoblin/tools/timesince.py:64
msgid "week" msgid "week"
msgstr "" msgstr "săptămâna"
#: mediagoblin/tools/timesince.py:65 #: mediagoblin/tools/timesince.py:65
msgid "day" msgid "day"
msgstr "" msgstr "ziua"
#: mediagoblin/tools/timesince.py:66 #: mediagoblin/tools/timesince.py:66
msgid "hour" msgid "hour"
msgstr "" msgstr "ora"
#: mediagoblin/tools/timesince.py:67 #: mediagoblin/tools/timesince.py:67
msgid "minute" msgid "minute"
msgstr "" msgstr "minutul"
#: mediagoblin/user_pages/forms.py:23 #: mediagoblin/user_pages/forms.py:23
msgid "Comment" msgid "Comment"
@ -1183,7 +1183,7 @@ msgstr "a făcut un comentariu la postarea ta"
#: mediagoblin/user_pages/views.py:169 #: mediagoblin/user_pages/views.py:169
msgid "Sorry, comments are disabled." msgid "Sorry, comments are disabled."
msgstr "" msgstr "Comentariile sunt dezactivate."
#: mediagoblin/user_pages/views.py:174 #: mediagoblin/user_pages/views.py:174
msgid "Oops, your comment was empty." msgid "Oops, your comment was empty."

View File

@ -10,8 +10,8 @@ msgstr ""
"Project-Id-Version: GNU MediaGoblin\n" "Project-Id-Version: GNU MediaGoblin\n"
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
"POT-Creation-Date: 2013-05-27 13:54-0500\n" "POT-Creation-Date: 2013-05-27 13:54-0500\n"
"PO-Revision-Date: 2013-05-27 18:54+0000\n" "PO-Revision-Date: 2013-06-01 21:08+0000\n"
"Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Last-Translator: aleksejrs <deletesoftware@yandex.ru>\n"
"Language-Team: Russian (http://www.transifex.com/projects/p/mediagoblin/language/ru/)\n" "Language-Team: Russian (http://www.transifex.com/projects/p/mediagoblin/language/ru/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -35,7 +35,7 @@ msgstr "Адрес электронной почты"
#: mediagoblin/auth/forms.py:41 #: mediagoblin/auth/forms.py:41
msgid "Username or Email" msgid "Username or Email"
msgstr "" msgstr "Имя пользователя или адрес электронной почты"
#: mediagoblin/auth/forms.py:52 #: mediagoblin/auth/forms.py:52
msgid "Username or email" msgid "Username or email"
@ -262,7 +262,7 @@ msgstr "Неправильный пароль"
#: mediagoblin/edit/views.py:363 #: mediagoblin/edit/views.py:363
msgid "Your password was changed successfully" msgid "Your password was changed successfully"
msgstr "" msgstr "Ваш пароль сменён успешно"
#: mediagoblin/gmg_commands/assetlink.py:60 #: mediagoblin/gmg_commands/assetlink.py:60
msgid "Cannot link theme... no theme set\n" msgid "Cannot link theme... no theme set\n"
@ -663,11 +663,11 @@ msgstr "Сохранить изменения"
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 #: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
#, python-format #, python-format
msgid "Changing %(username)s's password" msgid "Changing %(username)s's password"
msgstr "" msgstr "Смена пароля %(username)s"
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 #: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
msgid "Save" msgid "Save"
msgstr "" msgstr "Сохранить"
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 #: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
#, python-format #, python-format
@ -698,7 +698,7 @@ msgstr "Настройка учётной записи %(username)s"
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 #: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
msgid "Change your password." msgid "Change your password."
msgstr "" msgstr "Сменить пароль"
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 #: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
msgid "Delete my account" msgid "Delete my account"
@ -772,7 +772,7 @@ msgstr "Изображение «%(media_title)s»"
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 #: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
msgid "PDF file" msgid "PDF file"
msgstr "" msgstr "PDF-файл"
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 #: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
msgid "Toggle Rotate" msgid "Toggle Rotate"
@ -928,15 +928,15 @@ msgstr "Добавить этот комментарий"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 #: mediagoblin/templates/mediagoblin/user_pages/media.html:164
#, python-format #, python-format
msgid "%(formatted_time)s ago" msgid "%(formatted_time)s ago"
msgstr "" msgstr "%(formatted_time)s назад"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 #: mediagoblin/templates/mediagoblin/user_pages/media.html:150
msgid "Added" msgid "Added"
msgstr "" msgstr "Добавлен"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 #: mediagoblin/templates/mediagoblin/user_pages/media.html:161
msgid "Created" msgid "Created"
msgstr "" msgstr "Создан"
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
@ -1144,7 +1144,7 @@ msgstr ""
#: mediagoblin/tools/timesince.py:67 #: mediagoblin/tools/timesince.py:67
msgid "minute" msgid "minute"
msgstr "" msgstr "мин"
#: mediagoblin/user_pages/forms.py:23 #: mediagoblin/user_pages/forms.py:23
msgid "Comment" msgid "Comment"
@ -1183,7 +1183,7 @@ msgstr "оставил комментарий к вашему файлу"
#: mediagoblin/user_pages/views.py:169 #: mediagoblin/user_pages/views.py:169
msgid "Sorry, comments are disabled." msgid "Sorry, comments are disabled."
msgstr "" msgstr "Сожалеем: возможность комментирования отключена."
#: mediagoblin/user_pages/views.py:174 #: mediagoblin/user_pages/views.py:174
msgid "Oops, your comment was empty." msgid "Oops, your comment was empty."

View File

@ -14,8 +14,8 @@ msgstr ""
"Project-Id-Version: GNU MediaGoblin\n" "Project-Id-Version: GNU MediaGoblin\n"
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
"POT-Creation-Date: 2013-05-27 13:54-0500\n" "POT-Creation-Date: 2013-05-27 13:54-0500\n"
"PO-Revision-Date: 2013-05-27 18:54+0000\n" "PO-Revision-Date: 2013-05-28 07:47+0000\n"
"Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Last-Translator: martin <zatroch.martin@gmail.com>\n"
"Language-Team: Slovak (http://www.transifex.com/projects/p/mediagoblin/language/sk/)\n" "Language-Team: Slovak (http://www.transifex.com/projects/p/mediagoblin/language/sk/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -39,7 +39,7 @@ msgstr "Email adresse"
#: mediagoblin/auth/forms.py:41 #: mediagoblin/auth/forms.py:41
msgid "Username or Email" msgid "Username or Email"
msgstr "" msgstr "Použivateľské meno alebo e-mail"
#: mediagoblin/auth/forms.py:52 #: mediagoblin/auth/forms.py:52
msgid "Username or email" msgid "Username or email"
@ -266,7 +266,7 @@ msgstr "Nesprávne heslo"
#: mediagoblin/edit/views.py:363 #: mediagoblin/edit/views.py:363
msgid "Your password was changed successfully" msgid "Your password was changed successfully"
msgstr "" msgstr "Tvoje heslo bolo úspešne zmenené"
#: mediagoblin/gmg_commands/assetlink.py:60 #: mediagoblin/gmg_commands/assetlink.py:60
msgid "Cannot link theme... no theme set\n" msgid "Cannot link theme... no theme set\n"
@ -283,17 +283,17 @@ msgstr "Odstránené; hoci bol pôvodný symbolický odkaz adresára nájdený.\
#: mediagoblin/gmg_commands/assetlink.py:112 #: mediagoblin/gmg_commands/assetlink.py:112
#, python-format #, python-format
msgid "Could not link \"%s\": %s exists and is not a symlink\n" msgid "Could not link \"%s\": %s exists and is not a symlink\n"
msgstr "" msgstr "Nemožno odkazovať na \"%s\": %s existuje a nie je symbolickým odkazom\n"
#: mediagoblin/gmg_commands/assetlink.py:119 #: mediagoblin/gmg_commands/assetlink.py:119
#, python-format #, python-format
msgid "Skipping \"%s\"; already set up.\n" msgid "Skipping \"%s\"; already set up.\n"
msgstr "" msgstr "Preskakujem \"%s\"; opakovane nastavené.\n"
#: mediagoblin/gmg_commands/assetlink.py:124 #: mediagoblin/gmg_commands/assetlink.py:124
#, python-format #, python-format
msgid "Old link found for \"%s\"; removing.\n" msgid "Old link found for \"%s\"; removing.\n"
msgstr "" msgstr "Nájdený starý odkaz pre \"%s\"; odstraňujem.\n"
#: mediagoblin/meddleware/csrf.py:134 #: mediagoblin/meddleware/csrf.py:134
msgid "" msgid ""
@ -309,7 +309,7 @@ msgstr "Prepáč, nepodporujem tento typ súborov =("
#: mediagoblin/media_types/pdf/processing.py:136 #: mediagoblin/media_types/pdf/processing.py:136
msgid "unoconv failing to run, check log file" msgid "unoconv failing to run, check log file"
msgstr "" msgstr "beh unoconv zlyhal, preskúmajte log záznam"
#: mediagoblin/media_types/video/processing.py:37 #: mediagoblin/media_types/video/processing.py:37
msgid "Video transcoding failed" msgid "Video transcoding failed"
@ -620,7 +620,7 @@ msgid ""
"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
" or\n" " or\n"
" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" " <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
msgstr "" msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Vytvoriť účet na tejto stránke</a>\n alebo\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Nastaviť MediaGoblin na vlastnom serveri</a>"
#: mediagoblin/templates/mediagoblin/bits/logo.html:23 #: mediagoblin/templates/mediagoblin/bits/logo.html:23
#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 #: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
@ -667,11 +667,11 @@ msgstr "Uložiť zmeny"
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 #: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
#, python-format #, python-format
msgid "Changing %(username)s's password" msgid "Changing %(username)s's password"
msgstr "" msgstr "Mením heslo používateľa %(username)s"
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 #: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
msgid "Save" msgid "Save"
msgstr "" msgstr "Uložiť"
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 #: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
#, python-format #, python-format
@ -702,7 +702,7 @@ msgstr "Mením nastavenia účtu používateľa %(username)s"
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 #: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
msgid "Change your password." msgid "Change your password."
msgstr "" msgstr "Zmeniť svoje heslo."
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 #: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
msgid "Delete my account" msgid "Delete my account"
@ -776,7 +776,7 @@ msgstr "Obrázok pre %(media_title)s"
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 #: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
msgid "PDF file" msgid "PDF file"
msgstr "" msgstr "PDF súbor"
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 #: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
msgid "Toggle Rotate" msgid "Toggle Rotate"
@ -932,15 +932,15 @@ msgstr "Pridať tento komentár"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 #: mediagoblin/templates/mediagoblin/user_pages/media.html:164
#, python-format #, python-format
msgid "%(formatted_time)s ago" msgid "%(formatted_time)s ago"
msgstr "" msgstr "pred %(formatted_time)s "
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 #: mediagoblin/templates/mediagoblin/user_pages/media.html:150
msgid "Added" msgid "Added"
msgstr "" msgstr "Pridané"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 #: mediagoblin/templates/mediagoblin/user_pages/media.html:161
msgid "Created" msgid "Created"
msgstr "" msgstr "Vytvorené"
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
@ -1128,27 +1128,27 @@ msgstr "Zdá sa, že na tejto adrese sa nič nenachádza. Prepáč!</p><p>Pokia
#: mediagoblin/tools/timesince.py:62 #: mediagoblin/tools/timesince.py:62
msgid "year" msgid "year"
msgstr "" msgstr "rok"
#: mediagoblin/tools/timesince.py:63 #: mediagoblin/tools/timesince.py:63
msgid "month" msgid "month"
msgstr "" msgstr "mesiac"
#: mediagoblin/tools/timesince.py:64 #: mediagoblin/tools/timesince.py:64
msgid "week" msgid "week"
msgstr "" msgstr "týždeň"
#: mediagoblin/tools/timesince.py:65 #: mediagoblin/tools/timesince.py:65
msgid "day" msgid "day"
msgstr "" msgstr "deň"
#: mediagoblin/tools/timesince.py:66 #: mediagoblin/tools/timesince.py:66
msgid "hour" msgid "hour"
msgstr "" msgstr "hodina"
#: mediagoblin/tools/timesince.py:67 #: mediagoblin/tools/timesince.py:67
msgid "minute" msgid "minute"
msgstr "" msgstr "minúta"
#: mediagoblin/user_pages/forms.py:23 #: mediagoblin/user_pages/forms.py:23
msgid "Comment" msgid "Comment"
@ -1187,7 +1187,7 @@ msgstr "okmentoval tvoj príspevok"
#: mediagoblin/user_pages/views.py:169 #: mediagoblin/user_pages/views.py:169
msgid "Sorry, comments are disabled." msgid "Sorry, comments are disabled."
msgstr "" msgstr "Prepáč, komentovanie je vypnuté."
#: mediagoblin/user_pages/views.py:174 #: mediagoblin/user_pages/views.py:174
msgid "Oops, your comment was empty." msgid "Oops, your comment was empty."

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -5,14 +5,16 @@
# Translators: # Translators:
# <chc@citi.sinica.edu.tw>, 2011 # <chc@citi.sinica.edu.tw>, 2011
# Harry Chen <harryhow@gmail.com>, 2011-2012 # Harry Chen <harryhow@gmail.com>, 2011-2012
# medicalwei <medicalwei@gmail.com>, 2013
# medicalwei <medicalwei@gmail.com>, 2012 # medicalwei <medicalwei@gmail.com>, 2012
# m13253 <m13253@hotmail.com>, 2013
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: GNU MediaGoblin\n" "Project-Id-Version: GNU MediaGoblin\n"
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
"POT-Creation-Date: 2013-05-27 13:54-0500\n" "POT-Creation-Date: 2013-05-27 13:54-0500\n"
"PO-Revision-Date: 2013-05-27 18:54+0000\n" "PO-Revision-Date: 2013-06-16 01:40+0000\n"
"Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Last-Translator: m13253 <m13253@hotmail.com>\n"
"Language-Team: Chinese (Taiwan) (http://www.transifex.com/projects/p/mediagoblin/language/zh_TW/)\n" "Language-Team: Chinese (Taiwan) (http://www.transifex.com/projects/p/mediagoblin/language/zh_TW/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -36,7 +38,7 @@ msgstr "Email 位址"
#: mediagoblin/auth/forms.py:41 #: mediagoblin/auth/forms.py:41
msgid "Username or Email" msgid "Username or Email"
msgstr "" msgstr "使用者名稱或 email"
#: mediagoblin/auth/forms.py:52 #: mediagoblin/auth/forms.py:52
msgid "Username or email" msgid "Username or email"
@ -44,15 +46,15 @@ msgstr "使用者名稱或 email"
#: mediagoblin/auth/tools.py:31 #: mediagoblin/auth/tools.py:31
msgid "Invalid User name or email address." msgid "Invalid User name or email address."
msgstr "" msgstr "無效的使用者名稱或 email 位置。"
#: mediagoblin/auth/tools.py:32 #: mediagoblin/auth/tools.py:32
msgid "This field does not take email addresses." msgid "This field does not take email addresses."
msgstr "" msgstr "本欄位不接受 email 位置。"
#: mediagoblin/auth/tools.py:33 #: mediagoblin/auth/tools.py:33
msgid "This field requires an email address." msgid "This field requires an email address."
msgstr "" msgstr "本欄位需要 email 位置。"
#: mediagoblin/auth/views.py:54 #: mediagoblin/auth/views.py:54
msgid "Sorry, registration is disabled on this instance." msgid "Sorry, registration is disabled on this instance."
@ -92,11 +94,11 @@ msgstr "重送認證信。"
msgid "" msgid ""
"If that email address (case sensitive!) is registered an email has been sent" "If that email address (case sensitive!) is registered an email has been sent"
" with instructions on how to change your password." " with instructions on how to change your password."
msgstr "" msgstr "如果那 email 位置 (請注意大小寫) 已經註冊,寫有修改密碼步驟的 email 已經送出。"
#: mediagoblin/auth/views.py:269 #: mediagoblin/auth/views.py:269
msgid "Couldn't find someone with that username." msgid "Couldn't find someone with that username."
msgstr "" msgstr "找不到相關的使用者名稱。"
#: mediagoblin/auth/views.py:272 #: mediagoblin/auth/views.py:272
msgid "" msgid ""
@ -173,15 +175,15 @@ msgstr "本網址出錯了"
#: mediagoblin/edit/forms.py:63 #: mediagoblin/edit/forms.py:63
msgid "License preference" msgid "License preference"
msgstr "" msgstr "授權偏好"
#: mediagoblin/edit/forms.py:69 #: mediagoblin/edit/forms.py:69
msgid "This will be your default license on upload forms." msgid "This will be your default license on upload forms."
msgstr "" msgstr "在上傳頁面,這將會是您預設的授權模式。"
#: mediagoblin/edit/forms.py:71 #: mediagoblin/edit/forms.py:71
msgid "Email me when others comment on my media" msgid "Email me when others comment on my media"
msgstr "當有人對我的媒體評論時寄信給我" msgstr "當有人對我的媒體留言時寄信給我"
#: mediagoblin/edit/forms.py:83 #: mediagoblin/edit/forms.py:83
msgid "The title can't be empty" msgid "The title can't be empty"
@ -225,7 +227,7 @@ msgstr "您加上了附件「%s」"
#: mediagoblin/edit/views.py:182 #: mediagoblin/edit/views.py:182
msgid "You can only edit your own profile." msgid "You can only edit your own profile."
msgstr "" msgstr "您只能修改您自己的個人檔案。"
#: mediagoblin/edit/views.py:188 #: mediagoblin/edit/views.py:188
msgid "You are editing a user's profile. Proceed with caution." msgid "You are editing a user's profile. Proceed with caution."
@ -241,7 +243,7 @@ msgstr "帳號設定已儲存"
#: mediagoblin/edit/views.py:274 #: mediagoblin/edit/views.py:274
msgid "You need to confirm the deletion of your account." msgid "You need to confirm the deletion of your account."
msgstr "" msgstr "您必須要確認是否刪除您的帳號。"
#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 #: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
#: mediagoblin/user_pages/views.py:222 #: mediagoblin/user_pages/views.py:222
@ -263,7 +265,7 @@ msgstr "密碼錯誤"
#: mediagoblin/edit/views.py:363 #: mediagoblin/edit/views.py:363
msgid "Your password was changed successfully" msgid "Your password was changed successfully"
msgstr "" msgstr "您的密碼已經成功修改"
#: mediagoblin/gmg_commands/assetlink.py:60 #: mediagoblin/gmg_commands/assetlink.py:60
msgid "Cannot link theme... no theme set\n" msgid "Cannot link theme... no theme set\n"
@ -280,24 +282,24 @@ msgstr "但是舊的目錄連結已經找到並移除。\n"
#: mediagoblin/gmg_commands/assetlink.py:112 #: mediagoblin/gmg_commands/assetlink.py:112
#, python-format #, python-format
msgid "Could not link \"%s\": %s exists and is not a symlink\n" msgid "Could not link \"%s\": %s exists and is not a symlink\n"
msgstr "" msgstr "無法連結「%s」%s 存在,且不是符號連結\n"
#: mediagoblin/gmg_commands/assetlink.py:119 #: mediagoblin/gmg_commands/assetlink.py:119
#, python-format #, python-format
msgid "Skipping \"%s\"; already set up.\n" msgid "Skipping \"%s\"; already set up.\n"
msgstr "" msgstr "跳過「%s」已經建置完成。\n"
#: mediagoblin/gmg_commands/assetlink.py:124 #: mediagoblin/gmg_commands/assetlink.py:124
#, python-format #, python-format
msgid "Old link found for \"%s\"; removing.\n" msgid "Old link found for \"%s\"; removing.\n"
msgstr "" msgstr "找到「%s」舊的連結刪除中。\n"
#: mediagoblin/meddleware/csrf.py:134 #: mediagoblin/meddleware/csrf.py:134
msgid "" msgid ""
"CSRF cookie not present. This is most likely the result of a cookie blocker " "CSRF cookie not present. This is most likely the result of a cookie blocker "
"or somesuch.<br/>Make sure to permit the settings of cookies for this " "or somesuch.<br/>Make sure to permit the settings of cookies for this "
"domain." "domain."
msgstr "" msgstr "跨網站存取 (CSRF) 的 cookie 不存在,有可能是 cookie 阻擋程式之類的程式導致的。<br/>請允許此網域的 cookie 設定。"
#: mediagoblin/media_types/__init__.py:111 #: mediagoblin/media_types/__init__.py:111
#: mediagoblin/media_types/__init__.py:155 #: mediagoblin/media_types/__init__.py:155
@ -306,7 +308,7 @@ msgstr "抱歉,我不支援這樣的檔案格式 :("
#: mediagoblin/media_types/pdf/processing.py:136 #: mediagoblin/media_types/pdf/processing.py:136
msgid "unoconv failing to run, check log file" msgid "unoconv failing to run, check log file"
msgstr "" msgstr "unoconv 無法執行,請檢查紀錄檔"
#: mediagoblin/media_types/video/processing.py:37 #: mediagoblin/media_types/video/processing.py:37
msgid "Video transcoding failed" msgid "Video transcoding failed"
@ -335,7 +337,7 @@ msgstr "名稱"
#: mediagoblin/plugins/oauth/forms.py:35 #: mediagoblin/plugins/oauth/forms.py:35
msgid "The name of the OAuth client" msgid "The name of the OAuth client"
msgstr "OAuth client 的名稱" msgstr "OAuth 用戶程式的名稱"
#: mediagoblin/plugins/oauth/forms.py:36 #: mediagoblin/plugins/oauth/forms.py:36
msgid "Description" msgid "Description"
@ -359,7 +361,7 @@ msgid ""
" <strong>Public</strong> - The client can't make confidential\n" " <strong>Public</strong> - The client can't make confidential\n"
" requests to the GNU MediaGoblin instance (e.g. client-side\n" " requests to the GNU MediaGoblin instance (e.g. client-side\n"
" JavaScript client)." " JavaScript client)."
msgstr "<strong>秘密</strong> — OAuth client 可以對 GNU MediaGoblin 站台發送不被使用者代理攔截的請求 (例如伺服端的 client)。\n<strong>公開</strong> — OAuth client 無法對 GNU MediaGoblin 站台發送秘密的請求 (例如客戶端的 JavaScript client)。" msgstr "<strong>秘密</strong> — OAuth 用戶程式可以對 GNU MediaGoblin 站台發送不被使用者代理攔截的請求 (例如伺服端的用戶程式)。\n<strong>公開</strong> — OAuth 用戶程式無法對 GNU MediaGoblin 站台發送秘密的請求 (例如客戶端的 JavaScript 用戶程式)。"
#: mediagoblin/plugins/oauth/forms.py:52 #: mediagoblin/plugins/oauth/forms.py:52
msgid "Redirect URI" msgid "Redirect URI"
@ -369,23 +371,23 @@ msgstr "重定向 URI"
msgid "" msgid ""
"The redirect URI for the applications, this field\n" "The redirect URI for the applications, this field\n"
" is <strong>required</strong> for public clients." " is <strong>required</strong> for public clients."
msgstr "此應用程式的重定向 URI本欄位在公開類型的 OAuth client 為必填。" msgstr "此應用程式的重定向 URI本欄位在公開類型的 OAuth 用戶程式為必填。"
#: mediagoblin/plugins/oauth/forms.py:66 #: mediagoblin/plugins/oauth/forms.py:66
msgid "This field is required for public clients" msgid "This field is required for public clients"
msgstr "本欄位在公開類型的 OAuth client 為必填" msgstr "本欄位在公開類型的用戶程式為必填"
#: mediagoblin/plugins/oauth/views.py:56 #: mediagoblin/plugins/oauth/views.py:56
msgid "The client {0} has been registered!" msgid "The client {0} has been registered!"
msgstr "OAuth client {0} 註冊完成!" msgstr "OAuth 用戶程式 {0} 註冊完成!"
#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 #: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
msgid "OAuth client connections" msgid "OAuth client connections"
msgstr "" msgstr "OAuth 用戶程式連線"
#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 #: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
msgid "Your OAuth clients" msgid "Your OAuth clients"
msgstr "" msgstr "您的 OAuth 用戶程式"
#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 #: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
#: mediagoblin/templates/mediagoblin/submit/collection.html:30 #: mediagoblin/templates/mediagoblin/submit/collection.html:30
@ -450,7 +452,7 @@ msgstr "媒體處理面板"
#: mediagoblin/templates/mediagoblin/base.html:96 #: mediagoblin/templates/mediagoblin/base.html:96
msgid "Log out" msgid "Log out"
msgstr "" msgstr "登出"
#: mediagoblin/templates/mediagoblin/base.html:99 #: mediagoblin/templates/mediagoblin/base.html:99
#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 #: mediagoblin/templates/mediagoblin/user_pages/user.html:156
@ -577,7 +579,7 @@ msgstr "%(username)s 您好:\n\n要啟動 GNU MediaGoblin 帳號,請在您
msgid "" msgid ""
"Powered by <a href=\"http://mediagoblin.org/\" title='Version " "Powered by <a href=\"http://mediagoblin.org/\" title='Version "
"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." "%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
msgstr "" msgstr "本站使用 <a href=\"http://mediagoblin.org/\" title='Version %(version)s'>MediaGoblin</a>,這是一個 <a href=\"http://gnu.org/\">GNU</a> 專案。"
#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 #: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
#, python-format #, python-format
@ -617,7 +619,7 @@ msgid ""
"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
" or\n" " or\n"
" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" " <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
msgstr "" msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">在本站建立您的帳號</a>\n 或是\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">在您自己的伺服器上安裝 MediaGoblin</a>"
#: mediagoblin/templates/mediagoblin/bits/logo.html:23 #: mediagoblin/templates/mediagoblin/bits/logo.html:23
#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 #: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
@ -664,20 +666,20 @@ msgstr "儲存變更"
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 #: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
#, python-format #, python-format
msgid "Changing %(username)s's password" msgid "Changing %(username)s's password"
msgstr "" msgstr "更改 %(username)s 的密碼"
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 #: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
msgid "Save" msgid "Save"
msgstr "" msgstr "儲存"
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 #: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
#, python-format #, python-format
msgid "Really delete user '%(user_name)s' and all related media/comments?" msgid "Really delete user '%(user_name)s' and all related media/comments?"
msgstr "" msgstr "真的要刪除使用者「%(user_name)s」以及相關的媒體與留言"
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 #: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
msgid "Yes, really delete my account" msgid "Yes, really delete my account"
msgstr "" msgstr "是的,我真的要把我的帳號刪除"
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 #: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 #: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
@ -699,11 +701,11 @@ msgstr "正在改變 %(username)s 的帳號設定"
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 #: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
msgid "Change your password." msgid "Change your password."
msgstr "" msgstr "更改您的密碼。"
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 #: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
msgid "Delete my account" msgid "Delete my account"
msgstr "" msgstr "刪除我的帳號"
#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
#, python-format #, python-format
@ -722,7 +724,7 @@ msgstr "編輯 %(username)s 的個人檔案"
#: mediagoblin/templates/mediagoblin/listings/tag.html:35 #: mediagoblin/templates/mediagoblin/listings/tag.html:35
#, python-format #, python-format
msgid "Media tagged with: %(tag_name)s" msgid "Media tagged with: %(tag_name)s"
msgstr "此媒體被 tag 成%(tag_name)s" msgstr "這個媒體具有以下標籤%(tag_name)s"
#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 #: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 #: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
@ -773,7 +775,7 @@ msgstr " %(media_title)s 的照片"
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 #: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
msgid "PDF file" msgid "PDF file"
msgstr "" msgstr "PDF 檔"
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 #: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
msgid "Toggle Rotate" msgid "Toggle Rotate"
@ -781,7 +783,7 @@ msgstr "切換旋轉"
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 #: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
msgid "Perspective" msgid "Perspective"
msgstr "視" msgstr "視"
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 #: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 #: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
@ -820,14 +822,14 @@ msgid ""
"Sorry, this video will not work because\n" "Sorry, this video will not work because\n"
" your web browser does not support HTML5 \n" " your web browser does not support HTML5 \n"
" video." " video."
msgstr "" msgstr "抱歉,由於您的瀏覽器不支援 HTML5 影片,本影片無法播放"
#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 #: mediagoblin/templates/mediagoblin/media_displays/video.html:47
msgid "" msgid ""
"You can get a modern web browser that \n" "You can get a modern web browser that \n"
" can play this video at <a href=\"http://getfirefox.com\">\n" " can play this video at <a href=\"http://getfirefox.com\">\n"
" http://getfirefox.com</a>!" " http://getfirefox.com</a>!"
msgstr "" msgstr "您可以在 <a href=\"http://getfirefox.com\">http://getfirefox.com</a> 取得可以播放此影片的先進瀏覽器。"
#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 #: mediagoblin/templates/mediagoblin/media_displays/video.html:69
msgid "WebM file (640p; VP8/Vorbis)" msgid "WebM file (640p; VP8/Vorbis)"
@ -880,19 +882,19 @@ msgstr "移除"
#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 #: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
#, python-format #, python-format
msgid "%(username)s's collections" msgid "%(username)s's collections"
msgstr "" msgstr "%(username)s 的蒐藏"
#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 #: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
#, python-format #, python-format
msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
msgstr "" msgstr "<a href=\"%(user_url)s\">%(username)s</a> 的蒐藏"
#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 #: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
#, python-format #, python-format
msgid "" msgid ""
"Hi %(username)s,\n" "Hi %(username)s,\n"
"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" "%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
msgstr "%(username)s 您好:\n%(comment_author)s 在 %(instance_name)s 對您的內容 (%(comment_url)s) 張貼評論\n" msgstr "%(username)s 您好:\n%(comment_author)s 在 %(instance_name)s 對您的內容 (%(comment_url)s) 張貼留言\n"
#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 #: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
#, python-format #, python-format
@ -904,7 +906,7 @@ msgstr "%(username)s的媒體"
msgid "" msgid ""
"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " "<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
"href=\"%(tag_url)s\">%(tag)s</a>" "href=\"%(tag_url)s\">%(tag)s</a>"
msgstr "" msgstr "標籤為 <a href=\"%(tag_url)s\">%(tag)s</a> 的 <a href=\"%(user_url)s\">%(username)s</a> 的媒體"
#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 #: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
#, python-format #, python-format
@ -918,32 +920,32 @@ msgstr "❖ 瀏覽 <a href=\"%(user_url)s\">%(username)s</a> 的媒體"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 #: mediagoblin/templates/mediagoblin/user_pages/media.html:95
msgid "Add a comment" msgid "Add a comment"
msgstr "新增評論" msgstr "新增留言"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 #: mediagoblin/templates/mediagoblin/user_pages/media.html:104
msgid "Add this comment" msgid "Add this comment"
msgstr "增加評論" msgstr "增加留言"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 #: mediagoblin/templates/mediagoblin/user_pages/media.html:132
#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 #: mediagoblin/templates/mediagoblin/user_pages/media.html:152
#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 #: mediagoblin/templates/mediagoblin/user_pages/media.html:164
#, python-format #, python-format
msgid "%(formatted_time)s ago" msgid "%(formatted_time)s ago"
msgstr "" msgstr "%(formatted_time)s 前"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 #: mediagoblin/templates/mediagoblin/user_pages/media.html:150
msgid "Added" msgid "Added"
msgstr "" msgstr "新增於"
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 #: mediagoblin/templates/mediagoblin/user_pages/media.html:161
msgid "Created" msgid "Created"
msgstr "" msgstr "建立於"
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
#, python-format #, python-format
msgid "Add “%(media_title)s” to a collection" msgid "Add “%(media_title)s” to a collection"
msgstr "" msgstr "加入 “%(media_title)s” 至蒐藏"
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 #: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
msgid "+" msgid "+"
@ -1022,7 +1024,7 @@ msgstr "這個使用者(還)沒有填寫個人檔案。"
#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 #: mediagoblin/templates/mediagoblin/user_pages/user.html:124
msgid "Browse collections" msgid "Browse collections"
msgstr "" msgstr "瀏覽蒐藏"
#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 #: mediagoblin/templates/mediagoblin/user_pages/user.html:137
#, python-format #, python-format
@ -1047,11 +1049,11 @@ msgstr " (移除)"
#: mediagoblin/templates/mediagoblin/utils/collections.html:21 #: mediagoblin/templates/mediagoblin/utils/collections.html:21
msgid "Collected in" msgid "Collected in"
msgstr "" msgstr "蒐集了"
#: mediagoblin/templates/mediagoblin/utils/collections.html:40 #: mediagoblin/templates/mediagoblin/utils/collections.html:40
msgid "Add to a collection" msgid "Add to a collection"
msgstr "" msgstr "加入至蒐藏"
#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 #: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 #: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
@ -1125,31 +1127,31 @@ msgstr "不好意思,看起來這個網址上沒有網頁。</p><p>如果您
#: mediagoblin/tools/timesince.py:62 #: mediagoblin/tools/timesince.py:62
msgid "year" msgid "year"
msgstr "" msgstr ""
#: mediagoblin/tools/timesince.py:63 #: mediagoblin/tools/timesince.py:63
msgid "month" msgid "month"
msgstr "" msgstr ""
#: mediagoblin/tools/timesince.py:64 #: mediagoblin/tools/timesince.py:64
msgid "week" msgid "week"
msgstr "" msgstr ""
#: mediagoblin/tools/timesince.py:65 #: mediagoblin/tools/timesince.py:65
msgid "day" msgid "day"
msgstr "" msgstr ""
#: mediagoblin/tools/timesince.py:66 #: mediagoblin/tools/timesince.py:66
msgid "hour" msgid "hour"
msgstr "" msgstr "小時"
#: mediagoblin/tools/timesince.py:67 #: mediagoblin/tools/timesince.py:67
msgid "minute" msgid "minute"
msgstr "" msgstr ""
#: mediagoblin/user_pages/forms.py:23 #: mediagoblin/user_pages/forms.py:23
msgid "Comment" msgid "Comment"
msgstr "" msgstr "留言"
#: mediagoblin/user_pages/forms.py:25 #: mediagoblin/user_pages/forms.py:25
msgid "" msgid ""
@ -1168,7 +1170,7 @@ msgstr "我確定我要從蒐藏中移除此項目"
#: mediagoblin/user_pages/forms.py:39 #: mediagoblin/user_pages/forms.py:39
msgid "Collection" msgid "Collection"
msgstr "" msgstr "蒐藏"
#: mediagoblin/user_pages/forms.py:40 #: mediagoblin/user_pages/forms.py:40
msgid "-- Select --" msgid "-- Select --"
@ -1180,11 +1182,11 @@ msgstr "加註"
#: mediagoblin/user_pages/lib.py:58 #: mediagoblin/user_pages/lib.py:58
msgid "commented on your post" msgid "commented on your post"
msgstr "在您的內容張貼評論" msgstr "在您的內容張貼留言"
#: mediagoblin/user_pages/views.py:169 #: mediagoblin/user_pages/views.py:169
msgid "Sorry, comments are disabled." msgid "Sorry, comments are disabled."
msgstr "" msgstr "抱歉,留言被關閉。"
#: mediagoblin/user_pages/views.py:174 #: mediagoblin/user_pages/views.py:174
msgid "Oops, your comment was empty." msgid "Oops, your comment was empty."

View File

@ -58,15 +58,19 @@ def setup_global_and_app_config(config_path):
return global_config, app_config return global_config, app_config
def setup_database(): def setup_database(run_migrations=False):
app_config = mg_globals.app_config app_config = mg_globals.app_config
global_config = mg_globals.global_config
# Load all models for media types (plugins, ...) # Load all models for media types (plugins, ...)
load_models(app_config) load_models(app_config)
# Set up the database # Set up the database
db = setup_connection_and_db_from_config(app_config) db = setup_connection_and_db_from_config(app_config, run_migrations)
if run_migrations:
#Run the migrations to initialize/update the database.
from mediagoblin.gmg_commands.dbupdate import run_all_migrations
run_all_migrations(db, app_config, global_config)
else:
check_db_migrations_current(db) check_db_migrations_current(db)
setup_globals(database=db) setup_globals(database=db)

View File

@ -16,12 +16,18 @@
import os import os
import sys import sys
import logging
from celery import Celery from celery import Celery
from mediagoblin.tools.pluginapi import hook_runall from mediagoblin.tools.pluginapi import hook_runall
MANDATORY_CELERY_IMPORTS = ['mediagoblin.processing.task'] _log = logging.getLogger(__name__)
MANDATORY_CELERY_IMPORTS = [
'mediagoblin.processing.task',
'mediagoblin.notifications.task']
DEFAULT_SETTINGS_MODULE = 'mediagoblin.init.celery.dummy_settings_module' DEFAULT_SETTINGS_MODULE = 'mediagoblin.init.celery.dummy_settings_module'
@ -97,3 +103,13 @@ def setup_celery_from_config(app_config, global_config,
if set_environ: if set_environ:
os.environ['CELERY_CONFIG_MODULE'] = settings_module os.environ['CELERY_CONFIG_MODULE'] = settings_module
# Replace the default celery.current_app.conf if celery has already been
# initiated
from celery import current_app
_log.info('Setting celery configuration from object "{0}"'.format(
settings_module))
current_app.config_from_object(this_module)
_log.debug('Celery broker host: {0}'.format(current_app.conf['BROKER_HOST']))

View File

@ -14,6 +14,7 @@
# 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/>.
from mediagoblin import mg_globals
from mediagoblin.db.models import MediaEntry from mediagoblin.db.models import MediaEntry
from mediagoblin.db.util import media_entries_for_tag_slug from mediagoblin.db.util import media_entries_for_tag_slug
from mediagoblin.tools.pagination import Pagination from mediagoblin.tools.pagination import Pagination
@ -80,6 +81,17 @@ def atom_feed(request):
link = request.urlgen('index', qualified=True) link = request.urlgen('index', qualified=True)
feed_title += "for all recent items" feed_title += "for all recent items"
atomlinks = [
{'href': link,
'rel': 'alternate',
'type': 'text/html'}]
if mg_globals.app_config["push_urls"]:
for push_url in mg_globals.app_config["push_urls"]:
atomlinks.append({
'rel': 'hub',
'href': push_url})
cursor = cursor.order_by(MediaEntry.created.desc()) cursor = cursor.order_by(MediaEntry.created.desc())
cursor = cursor.limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS) cursor = cursor.limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS)
@ -87,9 +99,8 @@ def atom_feed(request):
feed_title, feed_title,
feed_url=request.url, feed_url=request.url,
id=link, id=link,
links=[{'href': link, links=atomlinks)
'rel': 'alternate',
'type': 'text/html'}])
for entry in cursor: for entry in cursor:
feed.add(entry.get('title'), feed.add(entry.get('title'),
entry.description_html, entry.description_html,

View File

@ -111,7 +111,7 @@ class CsrfMeddleware(BaseMeddleware):
httponly=True) httponly=True)
# update the Vary header # update the Vary header
response.vary = (getattr(response, 'vary', None) or []) + ['Cookie'] response.vary = list(getattr(response, 'vary', None) or []) + ['Cookie']
def _make_token(self, request): def _make_token(self, request):
"""Generate a new token to use for CSRF protection.""" """Generate a new token to use for CSRF protection."""

View File

@ -15,12 +15,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import os import os
import sys
import logging import logging
import tempfile import tempfile
from mediagoblin import mg_globals from mediagoblin.tools.pluginapi import hook_handle
from mediagoblin.tools.common import import_component
from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
_log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
@ -52,36 +50,6 @@ class MediaManagerBase(object):
return hasattr(self, i) return hasattr(self, i)
class CompatMediaManager(object):
def __init__(self, mm_dict, entry=None):
self.mm_dict = mm_dict
self.entry = entry
def __call__(self, entry):
"So this object can look like a class too, somehow"
assert self.entry is None
return self.__class__(self.mm_dict, entry)
def __getitem__(self, i):
return self.mm_dict[i]
def __contains__(self, i):
return (i in self.mm_dict)
@property
def media_fetch_order(self):
return self.mm_dict.get('media_fetch_order')
def sniff_handler(self, *args, **kwargs):
func = self.mm_dict.get("sniff_handler", None)
if func is not None:
return func(*args, **kwargs)
return False
def __getattr__(self, i):
return self.mm_dict[i]
def sniff_media(media): def sniff_media(media):
''' '''
Iterate through the enabled media types and find those suited Iterate through the enabled media types and find those suited
@ -98,11 +66,10 @@ def sniff_media(media):
media_file.write(media.stream.read()) media_file.write(media.stream.read())
media.stream.seek(0) media.stream.seek(0)
for media_type, manager in get_media_managers(): media_type = hook_handle('sniff_handler', media_file, media=media)
_log.info('Sniffing {0}'.format(media_type)) if media_type:
if manager.sniff_handler(media_file, media=media):
_log.info('{0} accepts the file'.format(media_type)) _log.info('{0} accepts the file'.format(media_type))
return media_type, manager return media_type, hook_handle(('media_manager', media_type))
else: else:
_log.debug('{0} did not accept the file'.format(media_type)) _log.debug('{0} did not accept the file'.format(media_type))
@ -111,27 +78,6 @@ def sniff_media(media):
_(u'Sorry, I don\'t support that file type :(')) _(u'Sorry, I don\'t support that file type :('))
def get_media_types():
"""
Generator, yields the available media types
"""
for media_type in mg_globals.app_config['media_types']:
yield media_type
def get_media_managers():
'''
Generator, yields all enabled media managers
'''
for media_type in get_media_types():
mm = import_component(media_type + ":MEDIA_MANAGER")
if isinstance(mm, dict):
mm = CompatMediaManager(mm)
yield media_type, mm
def get_media_type_and_manager(filename): def get_media_type_and_manager(filename):
''' '''
Try to find the media type based on the file name, extension Try to find the media type based on the file name, extension
@ -142,11 +88,10 @@ def get_media_type_and_manager(filename):
# Get the file extension # Get the file extension
ext = os.path.splitext(filename)[1].lower() ext = os.path.splitext(filename)[1].lower()
for media_type, manager in get_media_managers():
# Omit the dot from the extension and match it against # Omit the dot from the extension and match it against
# the media manager # the media manager
if ext[1:] in manager.accepted_extensions: if hook_handle('get_media_type_and_manager', ext[1:]):
return media_type, manager return hook_handle('get_media_type_and_manager', ext[1:])
else: else:
_log.info('File {0} has no file extension, let\'s hope the sniffers get it.'.format( _log.info('File {0} has no file extension, let\'s hope the sniffers get it.'.format(
filename)) filename))

View File

@ -17,15 +17,31 @@
from mediagoblin.media_types import MediaManagerBase from mediagoblin.media_types import MediaManagerBase
from mediagoblin.media_types.ascii.processing import process_ascii, \ from mediagoblin.media_types.ascii.processing import process_ascii, \
sniff_handler sniff_handler
from mediagoblin.tools import pluginapi
ACCEPTED_EXTENSIONS = ["txt", "asc", "nfo"]
MEDIA_TYPE = 'mediagoblin.media_types.ascii'
def setup_plugin():
config = pluginapi.get_config(MEDIA_TYPE)
class ASCIIMediaManager(MediaManagerBase): class ASCIIMediaManager(MediaManagerBase):
human_readable = "ASCII" human_readable = "ASCII"
processor = staticmethod(process_ascii) processor = staticmethod(process_ascii)
sniff_handler = staticmethod(sniff_handler)
display_template = "mediagoblin/media_displays/ascii.html" display_template = "mediagoblin/media_displays/ascii.html"
default_thumb = "images/media_thumbs/ascii.jpg" default_thumb = "images/media_thumbs/ascii.jpg"
accepted_extensions = ["txt", "asc", "nfo"]
MEDIA_MANAGER = ASCIIMediaManager def get_media_type_and_manager(ext):
if ext in ACCEPTED_EXTENSIONS:
return MEDIA_TYPE, ASCIIMediaManager
hooks = {
'setup': setup_plugin,
'get_media_type_and_manager': get_media_type_and_manager,
('media_manager', MEDIA_TYPE): lambda: ASCIIMediaManager,
'sniff_handler': sniff_handler,
}

View File

@ -28,17 +28,19 @@ from mediagoblin.media_types.ascii import asciitoimage
_log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
SUPPORTED_EXTENSIONS = ['txt', 'asc', 'nfo'] SUPPORTED_EXTENSIONS = ['txt', 'asc', 'nfo']
MEDIA_TYPE = 'mediagoblin.media_types.ascii'
def sniff_handler(media_file, **kw): def sniff_handler(media_file, **kw):
_log.info('Sniffing {0}'.format(MEDIA_TYPE))
if kw.get('media') is not None: if kw.get('media') is not None:
name, ext = os.path.splitext(kw['media'].filename) name, ext = os.path.splitext(kw['media'].filename)
clean_ext = ext[1:].lower() clean_ext = ext[1:].lower()
if clean_ext in SUPPORTED_EXTENSIONS: if clean_ext in SUPPORTED_EXTENSIONS:
return True return MEDIA_TYPE
return False return None
def process_ascii(proc_state): def process_ascii(proc_state):

View File

@ -17,14 +17,29 @@
from mediagoblin.media_types import MediaManagerBase from mediagoblin.media_types import MediaManagerBase
from mediagoblin.media_types.audio.processing import process_audio, \ from mediagoblin.media_types.audio.processing import process_audio, \
sniff_handler sniff_handler
from mediagoblin.tools import pluginapi
ACCEPTED_EXTENSIONS = ["mp3", "flac", "wav", "m4a"]
MEDIA_TYPE = 'mediagoblin.media_types.audio'
def setup_plugin():
config = pluginapi.get_config(MEDIA_TYPE)
class AudioMediaManager(MediaManagerBase): class AudioMediaManager(MediaManagerBase):
human_readable = "Audio" human_readable = "Audio"
processor = staticmethod(process_audio) processor = staticmethod(process_audio)
sniff_handler = staticmethod(sniff_handler)
display_template = "mediagoblin/media_displays/audio.html" display_template = "mediagoblin/media_displays/audio.html"
accepted_extensions = ["mp3", "flac", "wav", "m4a"]
MEDIA_MANAGER = AudioMediaManager def get_media_type_and_manager(ext):
if ext in ACCEPTED_EXTENSIONS:
return MEDIA_TYPE, AudioMediaManager
hooks = {
'setup': setup_plugin,
'get_media_type_and_manager': get_media_type_and_manager,
'sniff_handler': sniff_handler,
('media_manager', MEDIA_TYPE): lambda: AudioMediaManager,
}

View File

@ -27,19 +27,22 @@ from mediagoblin.media_types.audio.transcoders import (AudioTranscoder,
_log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
MEDIA_TYPE = 'mediagoblin.media_types.audio'
def sniff_handler(media_file, **kw): def sniff_handler(media_file, **kw):
_log.info('Sniffing {0}'.format(MEDIA_TYPE))
try: try:
transcoder = AudioTranscoder() transcoder = AudioTranscoder()
data = transcoder.discover(media_file.name) data = transcoder.discover(media_file.name)
except BadMediaFail: except BadMediaFail:
_log.debug('Audio discovery raised BadMediaFail') _log.debug('Audio discovery raised BadMediaFail')
return False return None
if data.is_audio == True and data.is_video == False: if data.is_audio == True and data.is_video == False:
return True return MEDIA_TYPE
return False return None
def process_audio(proc_state): def process_audio(proc_state):

View File

@ -13,21 +13,28 @@
# #
# 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/>.
import datetime import datetime
from mediagoblin.media_types import MediaManagerBase from mediagoblin.media_types import MediaManagerBase
from mediagoblin.media_types.image.processing import process_image, \ from mediagoblin.media_types.image.processing import process_image, \
sniff_handler sniff_handler
from mediagoblin.tools import pluginapi
ACCEPTED_EXTENSIONS = ["jpg", "jpeg", "png", "gif", "tiff"]
MEDIA_TYPE = 'mediagoblin.media_types.image'
def setup_plugin():
config = pluginapi.get_config('mediagoblin.media_types.image')
class ImageMediaManager(MediaManagerBase): class ImageMediaManager(MediaManagerBase):
human_readable = "Image" human_readable = "Image"
processor = staticmethod(process_image) processor = staticmethod(process_image)
sniff_handler = staticmethod(sniff_handler)
display_template = "mediagoblin/media_displays/image.html" display_template = "mediagoblin/media_displays/image.html"
default_thumb = "images/media_thumbs/image.png" default_thumb = "images/media_thumbs/image.png"
accepted_extensions = ["jpg", "jpeg", "png", "gif", "tiff"]
media_fetch_order = [u'medium', u'original', u'thumb'] media_fetch_order = [u'medium', u'original', u'thumb']
def get_original_date(self): def get_original_date(self):
@ -52,4 +59,14 @@ class ImageMediaManager(MediaManagerBase):
return None return None
MEDIA_MANAGER = ImageMediaManager def get_media_type_and_manager(ext):
if ext in ACCEPTED_EXTENSIONS:
return MEDIA_TYPE, ImageMediaManager
hooks = {
'setup': setup_plugin,
'get_media_type_and_manager': get_media_type_and_manager,
'sniff_handler': sniff_handler,
('media_manager', MEDIA_TYPE): lambda: ImageMediaManager,
}

View File

@ -35,6 +35,8 @@ PIL_FILTERS = {
'BICUBIC': Image.BICUBIC, 'BICUBIC': Image.BICUBIC,
'ANTIALIAS': Image.ANTIALIAS} 'ANTIALIAS': Image.ANTIALIAS}
MEDIA_TYPE = 'mediagoblin.media_types.image'
def resize_image(proc_state, resized, keyname, target_name, new_size, def resize_image(proc_state, resized, keyname, target_name, new_size,
exif_tags, workdir): exif_tags, workdir):
@ -95,17 +97,18 @@ def resize_tool(proc_state, force, keyname, target_name,
exif_tags, conversions_subdir) exif_tags, conversions_subdir)
SUPPORTED_FILETYPES = ['png', 'gif', 'jpg', 'jpeg'] SUPPORTED_FILETYPES = ['png', 'gif', 'jpg', 'jpeg', 'tiff']
def sniff_handler(media_file, **kw): def sniff_handler(media_file, **kw):
_log.info('Sniffing {0}'.format(MEDIA_TYPE))
if kw.get('media') is not None: # That's a double negative! if kw.get('media') is not None: # That's a double negative!
name, ext = os.path.splitext(kw['media'].filename) name, ext = os.path.splitext(kw['media'].filename)
clean_ext = ext[1:].lower() # Strip the . from ext and make lowercase clean_ext = ext[1:].lower() # Strip the . from ext and make lowercase
if clean_ext in SUPPORTED_FILETYPES: if clean_ext in SUPPORTED_FILETYPES:
_log.info('Found file extension in supported filetypes') _log.info('Found file extension in supported filetypes')
return True return MEDIA_TYPE
else: else:
_log.debug('Media present, extension not found in {0}'.format( _log.debug('Media present, extension not found in {0}'.format(
SUPPORTED_FILETYPES)) SUPPORTED_FILETYPES))
@ -113,7 +116,7 @@ def sniff_handler(media_file, **kw):
_log.warning('Need additional information (keyword argument \'media\')' _log.warning('Need additional information (keyword argument \'media\')'
' to be able to handle sniffing') ' to be able to handle sniffing')
return False return None
def process_image(proc_state): def process_image(proc_state):

View File

@ -17,15 +17,31 @@
from mediagoblin.media_types import MediaManagerBase from mediagoblin.media_types import MediaManagerBase
from mediagoblin.media_types.pdf.processing import process_pdf, \ from mediagoblin.media_types.pdf.processing import process_pdf, \
sniff_handler sniff_handler
from mediagoblin.tools import pluginapi
ACCEPTED_EXTENSIONS = ['pdf']
MEDIA_TYPE = 'mediagoblin.media_types.pdf'
def setup_plugin():
config = pluginapi.get_config(MEDIA_TYPE)
class PDFMediaManager(MediaManagerBase): class PDFMediaManager(MediaManagerBase):
human_readable = "PDF" human_readable = "PDF"
processor = staticmethod(process_pdf) processor = staticmethod(process_pdf)
sniff_handler = staticmethod(sniff_handler)
display_template = "mediagoblin/media_displays/pdf.html" display_template = "mediagoblin/media_displays/pdf.html"
default_thumb = "images/media_thumbs/pdf.jpg" default_thumb = "images/media_thumbs/pdf.jpg"
accepted_extensions = ["pdf"]
MEDIA_MANAGER = PDFMediaManager def get_media_type_and_manager(ext):
if ext in ACCEPTED_EXTENSIONS:
return MEDIA_TYPE, PDFMediaManager
hooks = {
'setup': setup_plugin,
'get_media_type_and_manager': get_media_type_and_manager,
'sniff_handler': sniff_handler,
('media_manager', MEDIA_TYPE): lambda: PDFMediaManager,
}

View File

@ -25,6 +25,8 @@ from mediagoblin.tools.translate import fake_ugettext_passthrough as _
_log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
MEDIA_TYPE = 'mediagoblin.media_types.pdf'
# TODO - cache (memoize) util # TODO - cache (memoize) util
# This is a list created via uniconv --show and hand removing some types that # This is a list created via uniconv --show and hand removing some types that
@ -163,16 +165,17 @@ def check_prerequisites():
return True return True
def sniff_handler(media_file, **kw): def sniff_handler(media_file, **kw):
_log.info('Sniffing {0}'.format(MEDIA_TYPE))
if not check_prerequisites(): if not check_prerequisites():
return False return None
if kw.get('media') is not None: if kw.get('media') is not None:
name, ext = os.path.splitext(kw['media'].filename) name, ext = os.path.splitext(kw['media'].filename)
clean_ext = ext[1:].lower() clean_ext = ext[1:].lower()
if clean_ext in supported_extensions(): if clean_ext in supported_extensions():
return True return MEDIA_TYPE
return False return None
def create_pdf_thumb(original, thumb_filename, width, height): def create_pdf_thumb(original, thumb_filename, width, height):
# Note: pdftocairo adds '.png', remove it # Note: pdftocairo adds '.png', remove it
@ -250,8 +253,8 @@ def process_pdf(proc_state):
else: else:
pdf_filename = queued_filename.rsplit('.', 1)[0] + '.pdf' pdf_filename = queued_filename.rsplit('.', 1)[0] + '.pdf'
unoconv = where('unoconv') unoconv = where('unoconv')
call(executable=unoconv, Popen(executable=unoconv,
args=[unoconv, '-v', '-f', 'pdf', queued_filename]) args=[unoconv, '-v', '-f', 'pdf', queued_filename]).wait()
if not os.path.exists(pdf_filename): if not os.path.exists(pdf_filename):
_log.debug('unoconv failed to convert file to pdf') _log.debug('unoconv failed to convert file to pdf')
raise BadMediaFail() raise BadMediaFail()

View File

@ -17,15 +17,30 @@
from mediagoblin.media_types import MediaManagerBase from mediagoblin.media_types import MediaManagerBase
from mediagoblin.media_types.stl.processing import process_stl, \ from mediagoblin.media_types.stl.processing import process_stl, \
sniff_handler sniff_handler
from mediagoblin.tools import pluginapi
MEDIA_TYPE = 'mediagoblin.media_types.stl'
ACCEPTED_EXTENSIONS = ["obj", "stl"]
def setup_plugin():
config = pluginapi.get_config(MEDIA_TYPE)
class STLMediaManager(MediaManagerBase): class STLMediaManager(MediaManagerBase):
human_readable = "stereo lithographics" human_readable = "stereo lithographics"
processor = staticmethod(process_stl) processor = staticmethod(process_stl)
sniff_handler = staticmethod(sniff_handler)
display_template = "mediagoblin/media_displays/stl.html" display_template = "mediagoblin/media_displays/stl.html"
default_thumb = "images/media_thumbs/video.jpg" default_thumb = "images/media_thumbs/video.jpg"
accepted_extensions = ["obj", "stl"]
MEDIA_MANAGER = STLMediaManager def get_media_type_and_manager(ext):
if ext in ACCEPTED_EXTENSIONS:
return MEDIA_TYPE, STLMediaManager
hooks = {
'setup': setup_plugin,
'get_media_type_and_manager': get_media_type_and_manager,
'sniff_handler': sniff_handler,
('media_manager', MEDIA_TYPE): lambda: STLMediaManager,
}

View File

@ -29,6 +29,7 @@ from mediagoblin.media_types.stl import model_loader
_log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
SUPPORTED_FILETYPES = ['stl', 'obj'] SUPPORTED_FILETYPES = ['stl', 'obj']
MEDIA_TYPE = 'mediagoblin.media_types.stl'
BLEND_FILE = pkg_resources.resource_filename( BLEND_FILE = pkg_resources.resource_filename(
'mediagoblin.media_types.stl', 'mediagoblin.media_types.stl',
@ -43,13 +44,14 @@ BLEND_SCRIPT = pkg_resources.resource_filename(
def sniff_handler(media_file, **kw): def sniff_handler(media_file, **kw):
_log.info('Sniffing {0}'.format(MEDIA_TYPE))
if kw.get('media') is not None: if kw.get('media') is not None:
name, ext = os.path.splitext(kw['media'].filename) name, ext = os.path.splitext(kw['media'].filename)
clean_ext = ext[1:].lower() clean_ext = ext[1:].lower()
if clean_ext in SUPPORTED_FILETYPES: if clean_ext in SUPPORTED_FILETYPES:
_log.info('Found file extension in supported filetypes') _log.info('Found file extension in supported filetypes')
return True return MEDIA_TYPE
else: else:
_log.debug('Media present, extension not found in {0}'.format( _log.debug('Media present, extension not found in {0}'.format(
SUPPORTED_FILETYPES)) SUPPORTED_FILETYPES))
@ -57,7 +59,7 @@ def sniff_handler(media_file, **kw):
_log.warning('Need additional information (keyword argument \'media\')' _log.warning('Need additional information (keyword argument \'media\')'
' to be able to handle sniffing') ' to be able to handle sniffing')
return False return None
def blender_render(config): def blender_render(config):

View File

@ -0,0 +1,27 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
from mediagoblin import mg_globals
_log = logging.getLogger(__name__)
def media_type_warning():
if mg_globals.app_config.get('media_types'):
_log.warning('Media_types have been converted to plugins. Old'
' media_types will no longer work. Please convert them'
' to plugins to continue using them.')

View File

@ -17,20 +17,35 @@
from mediagoblin.media_types import MediaManagerBase from mediagoblin.media_types import MediaManagerBase
from mediagoblin.media_types.video.processing import process_video, \ from mediagoblin.media_types.video.processing import process_video, \
sniff_handler sniff_handler
from mediagoblin.tools import pluginapi
MEDIA_TYPE = 'mediagoblin.media_types.video'
ACCEPTED_EXTENSIONS = [
"mp4", "mov", "webm", "avi", "3gp", "3gpp", "mkv", "ogv", "m4v"]
def setup_plugin():
config = pluginapi.get_config(MEDIA_TYPE)
class VideoMediaManager(MediaManagerBase): class VideoMediaManager(MediaManagerBase):
human_readable = "Video" human_readable = "Video"
processor = staticmethod(process_video) processor = staticmethod(process_video)
sniff_handler = staticmethod(sniff_handler)
display_template = "mediagoblin/media_displays/video.html" display_template = "mediagoblin/media_displays/video.html"
default_thumb = "images/media_thumbs/video.jpg" default_thumb = "images/media_thumbs/video.jpg"
accepted_extensions = [
"mp4", "mov", "webm", "avi", "3gp", "3gpp", "mkv", "ogv", "m4v"]
# Used by the media_entry.get_display_media method # Used by the media_entry.get_display_media method
media_fetch_order = [u'webm_640', u'original'] media_fetch_order = [u'webm_640', u'original']
default_webm_type = 'video/webm; codecs="vp8, vorbis"' default_webm_type = 'video/webm; codecs="vp8, vorbis"'
MEDIA_MANAGER = VideoMediaManager def get_media_type_and_manager(ext):
if ext in ACCEPTED_EXTENSIONS:
return MEDIA_TYPE, VideoMediaManager
hooks = {
'setup': setup_plugin,
'get_media_type_and_manager': get_media_type_and_manager,
'sniff_handler': sniff_handler,
('media_manager', MEDIA_TYPE): lambda: VideoMediaManager,
}

View File

@ -29,6 +29,8 @@ from .util import skip_transcode
_log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
_log.setLevel(logging.DEBUG) _log.setLevel(logging.DEBUG)
MEDIA_TYPE = 'mediagoblin.media_types.video'
class VideoTranscodingFail(BaseProcessingFail): class VideoTranscodingFail(BaseProcessingFail):
''' '''
@ -41,17 +43,18 @@ def sniff_handler(media_file, **kw):
transcoder = transcoders.VideoTranscoder() transcoder = transcoders.VideoTranscoder()
data = transcoder.discover(media_file.name) data = transcoder.discover(media_file.name)
_log.info('Sniffing {0}'.format(MEDIA_TYPE))
_log.debug('Discovered: {0}'.format(data)) _log.debug('Discovered: {0}'.format(data))
if not data: if not data:
_log.error('Could not discover {0}'.format( _log.error('Could not discover {0}'.format(
kw.get('media'))) kw.get('media')))
return False return None
if data['is_video'] == True: if data['is_video'] == True:
return True return MEDIA_TYPE
return False return None
def process_video(proc_state): def process_video(proc_state):

View File

@ -22,9 +22,15 @@ import logging
import urllib import urllib
import multiprocessing import multiprocessing
import gobject import gobject
old_argv = sys.argv
sys.argv = []
import pygst import pygst
pygst.require('0.10') pygst.require('0.10')
import gst import gst
sys.argv = old_argv
import struct import struct
try: try:
from PIL import Image from PIL import Image

View File

@ -0,0 +1,141 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
from mediagoblin.db.models import Notification, \
CommentNotification, CommentSubscription
from mediagoblin.notifications.task import email_notification_task
from mediagoblin.notifications.tools import generate_comment_message
_log = logging.getLogger(__name__)
def trigger_notification(comment, media_entry, request):
'''
Send out notifications about a new comment.
'''
subscriptions = CommentSubscription.query.filter_by(
media_entry_id=media_entry.id).all()
for subscription in subscriptions:
if not subscription.notify:
continue
if comment.get_author == subscription.user:
continue
cn = CommentNotification(
user_id=subscription.user_id,
subject_id=comment.id)
cn.save()
if subscription.send_email:
message = generate_comment_message(
subscription.user,
comment,
media_entry,
request)
email_notification_task.apply_async([cn.id, message])
def mark_notification_seen(notification):
if notification:
notification.seen = True
notification.save()
def mark_comment_notification_seen(comment_id, user):
notification = CommentNotification.query.filter_by(
user_id=user.id,
subject_id=comment_id).first()
_log.debug('Marking {0} as seen.'.format(notification))
mark_notification_seen(notification)
def get_comment_subscription(user_id, media_entry_id):
return CommentSubscription.query.filter_by(
user_id=user_id,
media_entry_id=media_entry_id).first()
def add_comment_subscription(user, media_entry):
'''
Create a comment subscription for a User on a MediaEntry.
Uses the User's wants_comment_notification to set email notifications for
the subscription to enabled/disabled.
'''
cn = get_comment_subscription(user.id, media_entry.id)
if not cn:
cn = CommentSubscription(
user_id=user.id,
media_entry_id=media_entry.id)
cn.notify = True
if not user.wants_comment_notification:
cn.send_email = False
cn.save()
def silence_comment_subscription(user, media_entry):
'''
Silence a subscription so that the user is never notified in any way about
new comments on an entry
'''
cn = get_comment_subscription(user.id, media_entry.id)
if cn:
cn.notify = False
cn.send_email = False
cn.save()
def remove_comment_subscription(user, media_entry):
cn = get_comment_subscription(user.id, media_entry.id)
if cn:
cn.delete()
NOTIFICATION_FETCH_LIMIT = 100
def get_notifications(user_id, only_unseen=True):
query = Notification.query.filter_by(user_id=user_id)
if only_unseen:
query = query.filter_by(seen=False)
notifications = query.limit(
NOTIFICATION_FETCH_LIMIT).all()
return notifications
def get_notification_count(user_id, only_unseen=True):
query = Notification.query.filter_by(user_id=user_id)
if only_unseen:
query = query.filter_by(seen=False)
count = query.count()
return count

View File

@ -0,0 +1,25 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from mediagoblin.tools.routing import add_route
add_route('mediagoblin.notifications.subscribe_comments',
'/u/<string:user>/m/<string:media>/notifications/subscribe/comments/',
'mediagoblin.notifications.views:subscribe_comments')
add_route('mediagoblin.notifications.silence_comments',
'/u/<string:user>/m/<string:media>/notifications/silence/',
'mediagoblin.notifications.views:silence_comments')

View File

@ -0,0 +1,46 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
from celery import registry
from celery.task import Task
from mediagoblin.tools.mail import send_email
from mediagoblin.db.models import CommentNotification
_log = logging.getLogger(__name__)
class EmailNotificationTask(Task):
'''
Celery notification task.
This task is executed by celeryd to offload long-running operations from
the web server.
'''
def run(self, notification_id, message):
cn = CommentNotification.query.filter_by(id=notification_id).first()
_log.info('Sending notification email about {0}'.format(cn))
return send_email(
message['from'],
[message['to']],
message['subject'],
message['body'])
email_notification_task = registry.tasks[EmailNotificationTask.name]

View File

@ -0,0 +1,55 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from mediagoblin.tools.template import render_template
from mediagoblin.tools.translate import pass_to_ugettext as _
from mediagoblin import mg_globals
def generate_comment_message(user, comment, media, request):
"""
Sends comment email to user when a comment is made on their media.
Args:
- user: the user object to whom the email is sent
- comment: the comment object referencing user's media
- media: the media object the comment is about
- request: the request
"""
comment_url = request.urlgen(
'mediagoblin.user_pages.media_home.view_comment',
comment=comment.id,
user=media.get_uploader.username,
media=media.slug_or_id,
qualified=True) + '#comment'
comment_author = comment.get_author.username
rendered_email = render_template(
request, 'mediagoblin/user_pages/comment_email.txt',
{'username': user.username,
'comment_author': comment_author,
'comment_content': comment.content,
'comment_url': comment_url})
return {
'from': mg_globals.app_config['email_sender_address'],
'to': user.email,
'subject': '{instance_title} - {comment_author} '.format(
comment_author=comment_author,
instance_title=mg_globals.app_config['html_title']) \
+ _('commented on your post'),
'body': rendered_email}

View File

@ -0,0 +1,54 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from mediagoblin.tools.response import render_to_response, render_404, redirect
from mediagoblin.tools.translate import pass_to_ugettext as _
from mediagoblin.decorators import (uses_pagination, get_user_media_entry,
get_media_entry_by_id,
require_active_login, user_may_delete_media, user_may_alter_collection,
get_user_collection, get_user_collection_item, active_user_from_url)
from mediagoblin import messages
from mediagoblin.notifications import add_comment_subscription, \
silence_comment_subscription
from werkzeug.exceptions import BadRequest
@get_user_media_entry
@require_active_login
def subscribe_comments(request, media):
add_comment_subscription(request.user, media)
messages.add_message(request,
messages.SUCCESS,
_('Subscribed to comments on %s!')
% media.title)
return redirect(request, location=media.url_for_self(request.urlgen))
@get_user_media_entry
@require_active_login
def silence_comments(request, media):
silence_comment_subscription(request.user, media)
messages.add_message(request,
messages.SUCCESS,
_('You will not receive notifications for comments on'
' %s.') % media.title)
return redirect(request, location=media.url_for_self(request.urlgen))

View File

@ -0,0 +1,88 @@
# 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.plugins.basic_auth import forms as auth_forms
from mediagoblin.plugins.basic_auth import tools as auth_tools
from mediagoblin.auth.tools import create_basic_user
from mediagoblin.db.models import User
from mediagoblin.tools import pluginapi
from sqlalchemy import or_
def setup_plugin():
config = pluginapi.get_config('mediagoblin.plugins.basic_auth')
def get_user(**kwargs):
username = kwargs.pop('username', None)
if username:
user = User.query.filter(
or_(
User.username == username,
User.email == username,
)).first()
return user
def create_user(registration_form):
user = get_user(username=registration_form.username.data)
if not user and 'password' in registration_form:
user = create_basic_user(registration_form)
user.pw_hash = gen_password_hash(
registration_form.password.data)
user.save()
return user
def get_login_form(request):
return auth_forms.LoginForm(request.form)
def get_registration_form(request):
return auth_forms.RegistrationForm(request.form)
def gen_password_hash(raw_pass, extra_salt=None):
return auth_tools.bcrypt_gen_password_hash(raw_pass, extra_salt)
def check_password(raw_pass, stored_hash, extra_salt=None):
if stored_hash:
return auth_tools.bcrypt_check_password(raw_pass,
stored_hash, extra_salt)
return None
def auth():
return True
def append_to_global_context(context):
context['pass_auth'] = True
return context
hooks = {
'setup': setup_plugin,
'authentication': auth,
'auth_get_user': get_user,
'auth_create_user': create_user,
'auth_get_login_form': get_login_form,
'auth_get_registration_form': get_registration_form,
'auth_gen_password_hash': gen_password_hash,
'auth_check_password': check_password,
'auth_fake_login_attempt': auth_tools.fake_login_attempt,
'template_global_context': append_to_global_context,
}

View File

@ -0,0 +1,46 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import wtforms
from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
from mediagoblin.auth.tools import normalize_user_or_email_field
class RegistrationForm(wtforms.Form):
username = wtforms.TextField(
_('Username'),
[wtforms.validators.Required(),
normalize_user_or_email_field(allow_email=False)])
password = wtforms.PasswordField(
_('Password'),
[wtforms.validators.Required(),
wtforms.validators.Length(min=5, max=1024)])
email = wtforms.TextField(
_('Email address'),
[wtforms.validators.Required(),
normalize_user_or_email_field(allow_user=False)])
class LoginForm(wtforms.Form):
username = wtforms.TextField(
_('Username or Email'),
[wtforms.validators.Required(),
normalize_user_or_email_field()])
password = wtforms.PasswordField(
_('Password'))
stay_logged_in = wtforms.BooleanField(
label='',
description=_('Stay logged in'))

View File

@ -13,14 +13,8 @@
# #
# 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/>.
import random
import bcrypt import bcrypt
import random
from mediagoblin.tools.mail import send_email
from mediagoblin.tools.template import render_template
from mediagoblin import mg_globals
def bcrypt_check_password(raw_pass, stored_hash, extra_salt=None): def bcrypt_check_password(raw_pass, stored_hash, extra_salt=None):
@ -88,33 +82,3 @@ def fake_login_attempt():
randplus_hashed_pass = bcrypt.hashpw(hashed_pass, rand_salt) randplus_hashed_pass = bcrypt.hashpw(hashed_pass, rand_salt)
randplus_stored_hash == randplus_hashed_pass randplus_stored_hash == randplus_hashed_pass
EMAIL_FP_VERIFICATION_TEMPLATE = (
u"http://{host}{uri}?"
u"userid={userid}&token={fp_verification_key}")
def send_fp_verification_email(user, request):
"""
Send the verification email to users to change their password.
Args:
- user: a user object
- request: the request
"""
rendered_email = render_template(
request, 'mediagoblin/auth/fp_verification_email.txt',
{'username': user.username,
'verification_url': EMAIL_FP_VERIFICATION_TEMPLATE.format(
host=request.host,
uri=request.urlgen('mediagoblin.auth.verify_forgot_password'),
userid=unicode(user.id),
fp_verification_key=user.fp_verification_key)})
# TODO: There is no error handling in place
send_email(
mg_globals.app_config['email_sender_address'],
[user.email],
'GNU MediaGoblin - Change forgotten password!',
rendered_email)

View File

@ -0,0 +1,123 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import uuid
from sqlalchemy import or_
from mediagoblin.auth.tools import create_basic_user
from mediagoblin.db.models import User
from mediagoblin.plugins.openid.models import OpenIDUserURL
from mediagoblin.tools import pluginapi
from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
PLUGIN_DIR = os.path.dirname(__file__)
def setup_plugin():
config = pluginapi.get_config('mediagoblin.plugins.openid')
routes = [
('mediagoblin.plugins.openid.register',
'/auth/openid/register/',
'mediagoblin.plugins.openid.views:register'),
('mediagoblin.plugins.openid.login',
'/auth/openid/login/',
'mediagoblin.plugins.openid.views:login'),
('mediagoblin.plugins.openid.finish_login',
'/auth/openid/login/finish/',
'mediagoblin.plugins.openid.views:finish_login'),
('mediagoblin.plugins.openid.edit',
'/edit/openid/',
'mediagoblin.plugins.openid.views:start_edit'),
('mediagoblin.plugins.openid.finish_edit',
'/edit/openid/finish/',
'mediagoblin.plugins.openid.views:finish_edit'),
('mediagoblin.plugins.openid.delete',
'/edit/openid/delete/',
'mediagoblin.plugins.openid.views:delete_openid'),
('mediagoblin.plugins.openid.finish_delete',
'/edit/openid/delete/finish/',
'mediagoblin.plugins.openid.views:finish_delete')]
pluginapi.register_routes(routes)
pluginapi.register_template_path(os.path.join(PLUGIN_DIR, 'templates'))
pluginapi.register_template_hooks(
{'register_link': 'mediagoblin/plugins/openid/register_link.html',
'login_link': 'mediagoblin/plugins/openid/login_link.html',
'edit_link': 'mediagoblin/plugins/openid/edit_link.html'})
def create_user(register_form):
if 'openid' in register_form:
username = register_form.username.data
user = User.query.filter(
or_(
User.username == username,
User.email == username,
)).first()
if not user:
user = create_basic_user(register_form)
new_entry = OpenIDUserURL()
new_entry.openid_url = register_form.openid.data
new_entry.user_id = user.id
new_entry.save()
return user
def extra_validation(register_form):
openid = register_form.openid.data if 'openid' in \
register_form else None
if openid:
openid_url_exists = OpenIDUserURL.query.filter_by(
openid_url=openid
).count()
extra_validation_passes = True
if openid_url_exists:
register_form.openid.errors.append(
_('Sorry, an account is already registered to that OpenID.'))
extra_validation_passes = False
return extra_validation_passes
def no_pass_redirect():
return 'openid'
def add_to_form_context(context):
context['openid_link'] = True
return context
def Auth():
return True
hooks = {
'setup': setup_plugin,
'authentication': Auth,
'auth_extra_validation': extra_validation,
'auth_create_user': create_user,
'auth_no_pass_redirect': no_pass_redirect,
('mediagoblin.auth.register',
'mediagoblin/auth/register.html'): add_to_form_context,
}

View File

@ -0,0 +1,41 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import wtforms
from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
from mediagoblin.auth.tools import normalize_user_or_email_field
class RegistrationForm(wtforms.Form):
openid = wtforms.HiddenField(
'',
[wtforms.validators.Required()])
username = wtforms.TextField(
_('Username'),
[wtforms.validators.Required(),
normalize_user_or_email_field(allow_email=False)])
email = wtforms.TextField(
_('Email address'),
[wtforms.validators.Required(),
normalize_user_or_email_field(allow_user=False)])
class LoginForm(wtforms.Form):
openid = wtforms.TextField(
_('OpenID'),
[wtforms.validators.Required(),
# Can openid's only be urls?
wtforms.validators.URL(message='Please enter a valid url.')])

View File

@ -0,0 +1,65 @@
# 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 import Column, Integer, Unicode, ForeignKey
from sqlalchemy.orm import relationship, backref
from mediagoblin.db.models import User
from mediagoblin.db.base import Base
class OpenIDUserURL(Base):
__tablename__ = "openid__user_urls"
id = Column(Integer, primary_key=True)
openid_url = Column(Unicode, nullable=False)
user_id = Column(Integer, ForeignKey(User.id), nullable=False)
# OpenID's are owned by their user, so do the full thing.
user = relationship(User, backref=backref('openid_urls',
cascade='all, delete-orphan'))
# OpenID Store Models
class Nonce(Base):
__tablename__ = "openid__nonce"
server_url = Column(Unicode, primary_key=True)
timestamp = Column(Integer, primary_key=True)
salt = Column(Unicode, primary_key=True)
def __unicode__(self):
return u'Nonce: %r, %r' % (self.server_url, self.salt)
class Association(Base):
__tablename__ = "openid__association"
server_url = Column(Unicode, primary_key=True)
handle = Column(Unicode, primary_key=True)
secret = Column(Unicode)
issued = Column(Integer)
lifetime = Column(Integer)
assoc_type = Column(Unicode)
def __unicode__(self):
return u'Association: %r, %r' % (self.server_url, self.handle)
MODELS = [
OpenIDUserURL,
Nonce,
Association
]

View File

@ -0,0 +1,127 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import base64
import time
from openid.association import Association as OIDAssociation
from openid.store.interface import OpenIDStore
from openid.store import nonce
from mediagoblin.plugins.openid.models import Association, Nonce
class SQLAlchemyOpenIDStore(OpenIDStore):
def __init__(self):
self.max_nonce_age = 6 * 60 * 60
def storeAssociation(self, server_url, association):
assoc = Association.query.filter_by(
server_url=server_url, handle=association.handle
).first()
if not assoc:
assoc = Association()
assoc.server_url = unicode(server_url)
assoc.handle = association.handle
# django uses base64 encoding, python-openid uses a blob field for
# secret
assoc.secret = unicode(base64.encodestring(association.secret))
assoc.issued = association.issued
assoc.lifetime = association.lifetime
assoc.assoc_type = association.assoc_type
assoc.save()
def getAssociation(self, server_url, handle=None):
assocs = []
if handle is not None:
assocs = Association.query.filter_by(
server_url=server_url, handle=handle
)
else:
assocs = Association.query.filter_by(
server_url=server_url
)
if assocs.count() == 0:
return None
else:
associations = []
for assoc in assocs:
association = OIDAssociation(
assoc.handle, base64.decodestring(assoc.secret),
assoc.issued, assoc.lifetime, assoc.assoc_type
)
if association.getExpiresIn() == 0:
assoc.delete()
else:
associations.append((association.issued, association))
if not associations:
return None
associations.sort()
return associations[-1][1]
def removeAssociation(self, server_url, handle):
assocs = Association.query.filter_by(
server_url=server_url, handle=handle
).first()
assoc_exists = True if assocs else False
for assoc in assocs:
assoc.delete()
return assoc_exists
def useNonce(self, server_url, timestamp, salt):
if abs(timestamp - time.time()) > nonce.SKEW:
return False
ononce = Nonce.query.filter_by(
server_url=server_url,
timestamp=timestamp,
salt=salt
).first()
if ononce:
return False
else:
ononce = Nonce()
ononce.server_url = server_url
ononce.timestamp = timestamp
ononce.salt = salt
ononce.save()
return True
def cleanupNonces(self, _now=None):
if _now is None:
_now = int(time.time())
expired = Nonce.query.filter(
Nonce.timestamp < (_now - nonce.SKEW)
)
count = expired.count()
for each in expired:
each.delete()
return count
def cleanupAssociations(self):
now = int(time.time())
assoc = Association.query.all()
count = 0
for each in assoc:
if (each.lifetime + each.issued) <= now:
each.delete()
count = count + 1
return count

View File

@ -0,0 +1,44 @@
{#
# 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/>.
#}
{% extends "mediagoblin/base.html" %}
{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
{% block title -%}
{% trans %}Add an OpenID{% endtrans %} &mdash; {{ super() }}
{%- endblock %}
{% block mediagoblin_content %}
<form action="{{ request.urlgen('mediagoblin.plugins.openid.edit') }}"
method="POST" enctype="multipart/form-data">
{{ csrf_token }}
<div class="form_box">
<h1>{% trans %}Add an OpenID{% endtrans %}</h1>
<p>
<a href="{{ request.urlgen('mediagoblin.plugins.openid.delete') }}">
{% trans %}Delete an OpenID{% endtrans %}
</a>
</p>
{{ wtforms_util.render_divs(form, True) }}
<div class="form_submit_buttons">
<input type="submit" value="{% trans %}Add{% endtrans %}" class="button_form"/>
</div>
</div>
</form>
{% endblock %}

View File

@ -0,0 +1,43 @@
{#
# 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/>.
#}
{% extends "mediagoblin/base.html" %}
{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
{% block title -%}
{% trans %}Delete an OpenID{% endtrans %} &mdash; {{ super() }}
{%- endblock %}
{% block mediagoblin_content %}
<form action="{{ request.urlgen('mediagoblin.plugins.openid.delete') }}"
method="POST" enctype="multipart/form-data">
{{ csrf_token }}
<div class="form_box">
<h1>{% trans %}Delete an OpenID{% endtrans %}</h1>
<p>
<a href="{{ request.urlgen('mediagoblin.plugins.openid.edit') }}">
{% trans %}Add an OpenID{% endtrans %}
</a>
</p>
{{ wtforms_util.render_divs(form, True) }}
<div class="form_submit_buttons">
<input type="submit" value="{% trans %}Delete{% endtrans %}" class="button_form"/>
</div>
</div>
</form>
{% endblock %}

View File

@ -0,0 +1,25 @@
{#
# 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/>.
#}
{% block openid_edit_link %}
<p>
<a href="{{ request.urlgen('mediagoblin.plugins.openid.edit') }}">
{% trans %}Edit your OpenID's{% endtrans %}
</a>
</p>
{% endblock %}

View File

@ -0,0 +1,65 @@
{#
# 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/>.
#}
{% extends "mediagoblin/base.html" %}
{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
{% block mediagoblin_head %}
<script type="text/javascript"
src="{{ request.staticdirect('/js/autofilledin_password.js') }}"></script>
{% endblock %}
{% block title -%}
{% trans %}Log in{% endtrans %} &mdash; {{ super() }}
{%- endblock %}
{% block mediagoblin_content %}
<form action="{{ post_url }}"
method="POST" enctype="multipart/form-data">
{{ csrf_token }}
<div class="form_box">
<h1>{% trans %}Log in{% endtrans %}</h1>
{% if login_failed %}
<div class="form_field_error">
{% trans %}Logging in failed!{% endtrans %}
</div>
{% endif %}
{% if allow_registration %}
<p>
{% trans %}Log in to create an account!{% endtrans %}
</p>
{% endif %}
{% if pass_auth is defined %}
<p>
<a href="{{ request.urlgen('mediagoblin.auth.login') }}?{{ request.query_string }}">
{%- trans %}Or login with a password!{% endtrans %}
</a>
</p>
{% endif %}
{{ wtforms_util.render_divs(login_form, True) }}
<div class="form_submit_buttons">
<input type="submit" value="{% trans %}Log in{% endtrans %}" class="button_form"/>
</div>
{% if next %}
<input type="hidden" name="next" value="{{ next }}" class="button_form"
style="display: none;"/>
{% endif %}
</div>
</form>
{% endblock %}

View File

@ -0,0 +1,25 @@
{#
# 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/>.
#}
{% block openid_login_link %}
<p>
<a href="{{ request.urlgen('mediagoblin.plugins.openid.login') }}?{{ request.query_string }}">
{%- trans %}Or login with OpenID!{% endtrans %}
</a>
</p>
{% endblock %}

View File

@ -0,0 +1,27 @@
{#
# 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/>.
#}
{% block openid_register_link %}
{% if openid_link is defined %}
<p>
<a href="{{ request.urlgen('mediagoblin.plugins.openid.login') }}">
{%- trans %}Or register with OpenID!{% endtrans %}
</a>
</p>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,24 @@
{#
# 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/>.
#}
{% extends "mediagoblin/base.html" %}
{% block mediagoblin_content %}
<div onload="document.getElementById('openid_message').submit()">
{{ html|safe }}
</div>
{% endblock %}

View File

@ -0,0 +1,404 @@
# 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 openid.consumer import consumer
from openid.consumer.discover import DiscoveryFailure
from openid.extensions.sreg import SRegRequest, SRegResponse
from mediagoblin import mg_globals, messages
from mediagoblin.db.models import User
from mediagoblin.decorators import (auth_enabled, allow_registration,
require_active_login)
from mediagoblin.tools.response import redirect, render_to_response
from mediagoblin.tools.translate import pass_to_ugettext as _
from mediagoblin.plugins.openid import forms as auth_forms
from mediagoblin.plugins.openid.models import OpenIDUserURL
from mediagoblin.plugins.openid.store import SQLAlchemyOpenIDStore
from mediagoblin.auth.tools import register_user
def _start_verification(request, form, return_to, sreg=True):
"""
Start OpenID Verification.
Returns False if verification fails, otherwise, will return either a
redirect or render_to_response object
"""
openid_url = form.openid.data
c = consumer.Consumer(request.session, SQLAlchemyOpenIDStore())
# Try to discover provider
try:
auth_request = c.begin(openid_url)
except DiscoveryFailure:
# Discovery failed, return to login page
form.openid.errors.append(
_('Sorry, the OpenID server could not be found'))
return False
host = 'http://' + request.host
if sreg:
# Ask provider for email and nickname
auth_request.addExtension(SRegRequest(required=['email', 'nickname']))
# Do we even need this?
if auth_request is None:
form.openid.errors.append(
_('No OpenID service was found for %s' % openid_url))
elif auth_request.shouldSendRedirect():
# Begin the authentication process as a HTTP redirect
redirect_url = auth_request.redirectURL(
host, return_to)
return redirect(
request, location=redirect_url)
else:
# Send request as POST
form_html = auth_request.htmlMarkup(
host, host + return_to,
# Is this necessary?
form_tag_attrs={'id': 'openid_message'})
# Beware: this renders a template whose content is a form
# and some javascript to submit it upon page load. Non-JS
# users will have to click the form submit button to
# initiate OpenID authentication.
return render_to_response(
request,
'mediagoblin/plugins/openid/request_form.html',
{'html': form_html})
return False
def _finish_verification(request):
"""
Complete OpenID Verification Process.
If the verification failed, will return false, otherwise, will return
the response
"""
c = consumer.Consumer(request.session, SQLAlchemyOpenIDStore())
# Check the response from the provider
response = c.complete(request.args, request.base_url)
if response.status == consumer.FAILURE:
messages.add_message(
request,
messages.WARNING,
_('Verification of %s failed: %s' %
(response.getDisplayIdentifier(), response.message)))
elif response.status == consumer.SUCCESS:
# Verification was successfull
return response
elif response.status == consumer.CANCEL:
# Verification canceled
messages.add_message(
request,
messages.WARNING,
_('Verification cancelled'))
return False
def _response_email(response):
""" Gets the email from the OpenID providers response"""
sreg_response = SRegResponse.fromSuccessResponse(response)
if sreg_response and 'email' in sreg_response:
return sreg_response.data['email']
return None
def _response_nickname(response):
""" Gets the nickname from the OpenID providers response"""
sreg_response = SRegResponse.fromSuccessResponse(response)
if sreg_response and 'nickname' in sreg_response:
return sreg_response.data['nickname']
return None
@auth_enabled
def login(request):
"""OpenID Login View"""
login_form = auth_forms.LoginForm(request.form)
allow_registration = mg_globals.app_config["allow_registration"]
# Can't store next in request.GET because of redirects to OpenID provider
# Store it in the session
next = request.GET.get('next')
request.session['next'] = next
login_failed = False
if request.method == 'POST' and login_form.validate():
return_to = request.urlgen(
'mediagoblin.plugins.openid.finish_login')
success = _start_verification(request, login_form, return_to)
if success:
return success
login_failed = True
return render_to_response(
request,
'mediagoblin/plugins/openid/login.html',
{'login_form': login_form,
'next': request.session.get('next'),
'login_failed': login_failed,
'post_url': request.urlgen('mediagoblin.plugins.openid.login'),
'allow_registration': allow_registration})
@auth_enabled
def finish_login(request):
"""Complete OpenID Login Process"""
response = _finish_verification(request)
if not response:
# Verification failed, redirect to login page.
return redirect(request, 'mediagoblin.plugins.openid.login')
# Verification was successfull
query = OpenIDUserURL.query.filter_by(
openid_url=response.identity_url,
).first()
user = query.user if query else None
if user:
# Set up login in session
request.session['user_id'] = unicode(user.id)
request.session.save()
if request.session.get('next'):
return redirect(request, location=request.session.pop('next'))
else:
return redirect(request, "index")
else:
# No user, need to register
if not mg_globals.app.auth:
messages.add_message(
request,
messages.WARNING,
_('Sorry, authentication is disabled on this instance.'))
return redirect(request, 'index')
# Get email and nickname from response
email = _response_email(response)
username = _response_nickname(response)
register_form = auth_forms.RegistrationForm(request.form,
openid=response.identity_url,
email=email,
username=username)
return render_to_response(
request,
'mediagoblin/auth/register.html',
{'register_form': register_form,
'post_url': request.urlgen('mediagoblin.plugins.openid.register')})
@allow_registration
@auth_enabled
def register(request):
"""OpenID Registration View"""
if request.method == 'GET':
# Need to connect to openid provider before registering a user to
# get the users openid url. If method is 'GET', then this page was
# acessed without logging in first.
return redirect(request, 'mediagoblin.plugins.openid.login')
register_form = auth_forms.RegistrationForm(request.form)
if register_form.validate():
user = register_user(request, register_form)
if user:
# redirect the user to their homepage... there will be a
# message waiting for them to verify their email
return redirect(
request, 'mediagoblin.user_pages.user_home',
user=user.username)
return render_to_response(
request,
'mediagoblin/auth/register.html',
{'register_form': register_form,
'post_url': request.urlgen('mediagoblin.plugins.openid.register')})
@require_active_login
def start_edit(request):
"""Starts the process of adding an openid url to a users account"""
form = auth_forms.LoginForm(request.form)
if request.method == 'POST' and form.validate():
query = OpenIDUserURL.query.filter_by(
openid_url=form.openid.data
).first()
user = query.user if query else None
if not user:
return_to = request.urlgen('mediagoblin.plugins.openid.finish_edit')
success = _start_verification(request, form, return_to, False)
if success:
return success
else:
form.openid.errors.append(
_('Sorry, an account is already registered to that OpenID.'))
return render_to_response(
request,
'mediagoblin/plugins/openid/add.html',
{'form': form,
'post_url': request.urlgen('mediagoblin.plugins.openid.edit')})
@require_active_login
def finish_edit(request):
"""Finishes the process of adding an openid url to a user"""
response = _finish_verification(request)
if not response:
# Verification failed, redirect to add openid page.
return redirect(request, 'mediagoblin.plugins.openid.edit')
# Verification was successfull
query = OpenIDUserURL.query.filter_by(
openid_url=response.identity_url,
).first()
user_exists = query.user if query else None
if user_exists:
# user exists with that openid url, redirect back to edit page
messages.add_message(
request,
messages.WARNING,
_('Sorry, an account is already registered to that OpenID.'))
return redirect(request, 'mediagoblin.plugins.openid.edit')
else:
# Save openid to user
user = User.query.filter_by(
id=request.session['user_id']
).first()
new_entry = OpenIDUserURL()
new_entry.openid_url = response.identity_url
new_entry.user_id = user.id
new_entry.save()
messages.add_message(
request,
messages.SUCCESS,
_('Your OpenID url was saved successfully.'))
return redirect(request, 'mediagoblin.edit.account')
@require_active_login
def delete_openid(request):
"""View to remove an openid from a users account"""
form = auth_forms.LoginForm(request.form)
if request.method == 'POST' and form.validate():
# Check if a user has this openid
query = OpenIDUserURL.query.filter_by(
openid_url=form.openid.data
)
user = query.first().user if query.first() else None
if user and user.id == int(request.session['user_id']):
count = len(user.openid_urls)
if not count > 1 and not user.pw_hash:
# Make sure the user has a pw or another OpenID
messages.add_message(
request,
messages.WARNING,
_("You can't delete your only OpenID URL unless you"
" have a password set"))
elif user:
# There is a user, but not the same user who is logged in
form.openid.errors.append(
_('That OpenID is not registered to this account.'))
if not form.errors and not request.session.get('messages'):
# Okay to continue with deleting openid
return_to = request.urlgen(
'mediagoblin.plugins.openid.finish_delete')
success = _start_verification(request, form, return_to, False)
if success:
return success
return render_to_response(
request,
'mediagoblin/plugins/openid/delete.html',
{'form': form,
'post_url': request.urlgen('mediagoblin.plugins.openid.delete')})
@require_active_login
def finish_delete(request):
"""Finishes the deletion of an OpenID from an user's account"""
response = _finish_verification(request)
if not response:
# Verification failed, redirect to delete openid page.
return redirect(request, 'mediagoblin.plugins.openid.delete')
query = OpenIDUserURL.query.filter_by(
openid_url=response.identity_url
)
user = query.first().user if query.first() else None
# Need to check this again because of generic openid urls such as google's
if user and user.id == int(request.session['user_id']):
count = len(user.openid_urls)
if count > 1 or user.pw_hash:
# User has more then one openid or also has a password.
query.first().delete()
messages.add_message(
request,
messages.SUCCESS,
_('OpenID was successfully removed.'))
return redirect(request, 'mediagoblin.edit.account')
elif not count > 1:
messages.add_message(
request,
messages.WARNING,
_("You can't delete your only OpenID URL unless you have a "
"password set"))
return redirect(request, 'mediagoblin.plugins.openid.delete')
else:
messages.add_message(
request,
messages.WARNING,
_('That OpenID is not registered to this account.'))
return redirect(request, 'mediagoblin.plugins.openid.delete')

View File

@ -35,6 +35,7 @@ def get_url_map():
import mediagoblin.edit.routing import mediagoblin.edit.routing
import mediagoblin.webfinger.routing import mediagoblin.webfinger.routing
import mediagoblin.listings.routing import mediagoblin.listings.routing
import mediagoblin.notifications.routing
for route in PluginManager().get_routes(): for route in PluginManager().get_routes():
add_route(*route) add_route(*route)

View File

@ -129,6 +129,7 @@ header {
.header_dropdown { .header_dropdown {
margin-bottom: 20px; margin-bottom: 20px;
padding: 0px 10px 0px 10px;
} }
.header_dropdown li { .header_dropdown li {
@ -333,6 +334,10 @@ text-align: center;
width: 20px; width: 20px;
} }
.boolean {
margin-bottom: 8px;
}
textarea#description, textarea#bio { textarea#description, textarea#bio {
resize: vertical; resize: vertical;
height: 100px; height: 100px;
@ -384,6 +389,12 @@ a.comment_whenlink:hover, a.report_whenlink:hover {
margin-top: 8px; margin-top: 8px;
} }
.comment_active {
box-shadow: 0px 0px 15px 15px #378566;
background: #378566;
color: #f7f7f7;
}
textarea#comment_content { textarea#comment_content {
resize: vertical; resize: vertical;
width: 100%; width: 100%;

Some files were not shown because too many files have changed in this diff Show More