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:
commit
52a355b275
17
AUTHORS
17
AUTHORS
@ -8,10 +8,12 @@ variety of different ways and this software wouldn't exist without them.
|
||||
Thank you!
|
||||
|
||||
* Aaron Williamson
|
||||
* Aditi Mittal
|
||||
* Aeva Ntsc
|
||||
* Alejandro Villanueva
|
||||
* Aleksandar Micovic
|
||||
* Aleksej Serdjukov
|
||||
* Alon Levy
|
||||
* Alex Camelio
|
||||
* András Veres-Szentkirályi
|
||||
* Bassam Kurdali
|
||||
@ -21,13 +23,17 @@ Thank you!
|
||||
* Corey Farwell
|
||||
* Chris Moylan
|
||||
* Christopher Allan Webber
|
||||
* David Thompson
|
||||
* Daniel Neel
|
||||
* Deb Nicholson
|
||||
* Derek Moore
|
||||
* Duncan Paterson
|
||||
* Elrond of Samba TNG
|
||||
* Emily O'Leary
|
||||
* Gabi Thume
|
||||
* Gabriel Saldana
|
||||
* Greg Grossmeier
|
||||
* Hans Lo
|
||||
* Jakob Kramer
|
||||
* Jef van Schendel
|
||||
* Jessica Tallon
|
||||
@ -36,25 +42,34 @@ Thank you!
|
||||
* Jorge Araya Navarro
|
||||
* Karen Rustad
|
||||
* Kuno Woudt
|
||||
* Laura Arjona
|
||||
* Larisa Hoffenbecker
|
||||
* Luke Slater
|
||||
* Manuel Urbano Santos
|
||||
* Mark Holmquist
|
||||
* Mats Sjöberg
|
||||
* Matt Lee
|
||||
* Michele Azzolari
|
||||
* Mike Linksvayer
|
||||
* Natalie Foust-Pilcher
|
||||
* Nathan Yergler
|
||||
* Odin Hørthe Omdal
|
||||
* Osama Khalid
|
||||
* Pablo J. Urbano Santos
|
||||
* Praveen Kumar
|
||||
* Rasmus Larsson
|
||||
* Rodney Ewing
|
||||
* Runar Petursson
|
||||
* Sacha De'Angeli
|
||||
* Sam Kleinman
|
||||
* Sam Tuke
|
||||
* Sebastian Spaeth
|
||||
* Shawn Khan
|
||||
* Simon Fondrie-Teitler
|
||||
* Stefano Zacchiroli
|
||||
* Tiberiu C. Turbureanu
|
||||
* Tran Thanh Bao
|
||||
* Tryggvi Björgvinsson
|
||||
* Shawn Khan
|
||||
* Will Kahn-Greene
|
||||
|
||||
@ -64,4 +79,4 @@ If you think your name should be on this list, let us know!
|
||||
We also are currently borrowing an image in
|
||||
mediagoblin/static/images/media_thumbs/image.png from the wonderful
|
||||
people at http://tango.freedesktop.org/ which is in the public
|
||||
domain... thanks Tango folks!
|
||||
domain... thanks Tango folks!
|
||||
|
14
README
14
README
@ -5,21 +5,21 @@
|
||||
What is GNU MediaGoblin?
|
||||
========================
|
||||
|
||||
* Initially, a place to store all your photos that’s as awesome as, if
|
||||
not more awesome than, existing network services (Flickr, SmugMug,
|
||||
Picasa, etc)
|
||||
* A place to store all your different media (photos, videos, audios,
|
||||
and more!) that’s as awesome as, if not more awesome than, existing
|
||||
network services (Flickr, YouTube, etc)
|
||||
* Customizable!
|
||||
* A place for people to collaborate and show off original and derived
|
||||
creations. Free, as in freedom. We’re a GNU project after all.
|
||||
* Later, a place for all sorts of media, such as video, music, etc hosting.
|
||||
* Later, federated with OStatus!
|
||||
* Extensible: Plugins allow you to add new media types (3d models?
|
||||
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?
|
||||
==========================
|
||||
|
||||
Yes! But with caveats. The software is usable and there are instances
|
||||
running, but it's still in its early stages.
|
||||
Yes!
|
||||
|
||||
|
||||
Can I help/hang out/participate/whisper sweet nothings in your ear?
|
||||
|
62
docs/source/devel/migrations.rst
Normal file
62
docs/source/devel/migrations.rst
Normal 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!
|
@ -74,6 +74,7 @@ This guide covers writing new GNU MediaGoblin plugins.
|
||||
pluginwriter/database
|
||||
pluginwriter/api
|
||||
pluginwriter/tests
|
||||
pluginwriter/media_type_hooks
|
||||
|
||||
|
||||
Part 4: Developer's Zone
|
||||
@ -87,6 +88,7 @@ This chapter contains various information for developers.
|
||||
devel/codebase
|
||||
devel/storage
|
||||
devel/originaldesigndecisions
|
||||
devel/migrations
|
||||
|
||||
|
||||
Indices and tables
|
||||
|
@ -69,6 +69,32 @@ example might look like::
|
||||
This means that when people enable your plugin in their config you'll
|
||||
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
|
||||
-------------
|
||||
|
38
docs/source/pluginwriter/media_type_hooks.rst
Normal file
38
docs/source/pluginwriter/media_type_hooks.rst
Normal 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.
|
@ -157,13 +157,14 @@ directory. Modify these commands to reflect your own environment::
|
||||
mkdir -p /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
|
||||
cd mediagoblin
|
||||
git submodule init && git submodule update
|
||||
|
||||
And set up the in-package virtualenv::
|
||||
|
||||
cd mediagoblin
|
||||
(virtualenv --system-site-packages . || virtualenv .) && ./bin/python setup.py develop
|
||||
|
||||
.. note::
|
||||
@ -194,7 +195,7 @@ This concludes the initial configuration of the development
|
||||
environment. In the future, when you update your
|
||||
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
|
||||
configuration, you may need to stop it first or the dbupdate command
|
||||
|
@ -18,16 +18,18 @@ Media Types
|
||||
====================
|
||||
|
||||
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
|
||||
and ascii art.
|
||||
but in the meanwhile there are five additional media types: video, audio,
|
||||
ascii art, STL/3d models, PDF and Document.
|
||||
|
||||
First, you should probably read ":doc:`configuration`" to make sure
|
||||
you know how to modify the mediagoblin config file.
|
||||
|
||||
|
||||
Enabling Media Types
|
||||
====================
|
||||
|
||||
.. note::
|
||||
Media types are now plugins
|
||||
|
||||
Media types are enabled in your mediagoblin configuration file, typically it is
|
||||
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
|
||||
@ -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
|
||||
on this page.
|
||||
|
||||
To enable a media type, edit the ``media_types`` list in your
|
||||
``mediagoblin_local.ini``. For example, if your system supported image and
|
||||
video media types, then the list would look like this::
|
||||
To enable a media type, add the the media type under the ``[plugins]`` section
|
||||
in you ``mediagoblin_local.ini``. For example, if your system supported image
|
||||
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::
|
||||
|
||||
@ -83,8 +87,8 @@ good/bad/ugly). On Debianoid systems
|
||||
gstreamer0.10-ffmpeg
|
||||
|
||||
|
||||
Add ``mediagoblin.media_types.video`` to the ``media_types`` list in your
|
||||
``mediagoblin_local.ini`` and restart MediaGoblin.
|
||||
Add ``[[mediagoblin.media_types.video]]`` under the ``[plugins]`` section in
|
||||
your ``mediagoblin_local.ini`` and restart MediaGoblin.
|
||||
|
||||
Run
|
||||
|
||||
@ -133,7 +137,7 @@ Then install ``scikits.audiolab`` for the spectrograms::
|
||||
|
||||
./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.
|
||||
|
||||
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
|
||||
``mediagoblin_local.ini``. In the ``[mediagoblin]`` section, add
|
||||
``mediagoblin.media_types.ascii`` to the ``media_types`` list.
|
||||
|
||||
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
|
||||
``mediagoblin_local.ini``. In the ``[plugins]`` section, add
|
||||
``[[mediagoblin.media_types.ascii]]``.
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
Run
|
||||
@ -199,8 +198,16 @@ will be able to present them to your wide audience of admirers!
|
||||
PDF and Document
|
||||
================
|
||||
|
||||
To enable the "PDF and Document" support plugin, you need pdftocairo, pdfinfo,
|
||||
unoconv with headless support. All executables must be on your execution path.
|
||||
To enable the "PDF and Document" support plugin, you need:
|
||||
|
||||
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:
|
||||
|
||||
@ -208,6 +215,9 @@ To install this on Fedora:
|
||||
|
||||
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:
|
||||
|
||||
.. 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.
|
||||
|
||||
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.
|
||||
|
||||
Run
|
||||
|
@ -21,11 +21,28 @@ This chapter has important information for releases in it.
|
||||
If you're upgrading from a previous release, please read 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
|
||||
=====
|
||||
|
||||
**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.
|
||||
3. Note that ``./bin/gmg theme assetlink`` is now just
|
||||
``./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/;
|
||||
}
|
||||
|
||||
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**
|
||||
|
||||
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
|
||||
your site.
|
||||
|
||||
|
||||
**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
|
||||
|
@ -11,19 +11,15 @@ email_sender_address = "notice@mediagoblin.example.org"
|
||||
|
||||
## Uncomment and change to your DB's appropiate setting.
|
||||
## Default is a local sqlite db "mediagoblin.db".
|
||||
## Don't forget to run `./bin/gmg dbupdate` after having changed it.
|
||||
# sql_engine = postgresql:///mediagoblin
|
||||
|
||||
# set to false to enable sending notices
|
||||
# Set to false to enable sending notices
|
||||
email_debug_mode = true
|
||||
|
||||
# Set to false to disable registrations
|
||||
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
|
||||
# local_templates = %(here)s/user_dev/templates/
|
||||
|
||||
@ -43,7 +39,9 @@ base_url = /mgoblin_media/
|
||||
[celery]
|
||||
# Put celery stuff here
|
||||
|
||||
# place plugins here---each in their own subsection of [plugins]. see
|
||||
# documentation for details.
|
||||
# Place plugins here, each in their own subsection of [plugins].
|
||||
# See http://docs.mediagoblin.org/siteadmin/plugins.html for details.
|
||||
[plugins]
|
||||
[[mediagoblin.plugins.geolocation]]
|
||||
[[mediagoblin.plugins.basic_auth]]
|
||||
[[mediagoblin.media_types.image]]
|
||||
|
@ -23,4 +23,4 @@
|
||||
|
||||
# see http://www.python.org/dev/peps/pep-0386/
|
||||
|
||||
__version__ = "0.4.0.dev"
|
||||
__version__ = "0.5.0.dev"
|
||||
|
@ -29,6 +29,7 @@ from mediagoblin.tools import common, session, translate, template
|
||||
from mediagoblin.tools.response import render_http_exception
|
||||
from mediagoblin.tools.theme import register_themes
|
||||
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.init.celery import setup_celery_from_config
|
||||
from mediagoblin.init.plugins import setup_plugins
|
||||
@ -37,6 +38,8 @@ from mediagoblin.init import (get_jinja_loader, get_staticdirector,
|
||||
setup_storage)
|
||||
from mediagoblin.tools.pluginapi import PluginManager, hook_transform
|
||||
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__)
|
||||
@ -67,6 +70,8 @@ class MediaGoblinApp(object):
|
||||
# Open and setup the config
|
||||
global_config, app_config = setup_global_and_app_config(config_path)
|
||||
|
||||
media_type_warning()
|
||||
|
||||
setup_crypto()
|
||||
|
||||
##########################################
|
||||
@ -85,7 +90,7 @@ class MediaGoblinApp(object):
|
||||
setup_plugins()
|
||||
|
||||
# Set up the database
|
||||
self.db = setup_database()
|
||||
self.db = setup_database(app_config['run_migrations'])
|
||||
|
||||
# Register themes
|
||||
self.theme_registry, self.current_theme = register_themes(app_config)
|
||||
@ -97,6 +102,11 @@ class MediaGoblinApp(object):
|
||||
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
|
||||
self.public_store, self.queue_store = setup_storage()
|
||||
|
||||
@ -186,6 +196,11 @@ class MediaGoblinApp(object):
|
||||
|
||||
request.urlgen = build_proxy
|
||||
|
||||
# Log user out if authentication_disabled
|
||||
no_auth_logout(request)
|
||||
|
||||
request.notifications = notifications
|
||||
|
||||
mg_request.setup_user_in_request(request)
|
||||
|
||||
request.controller_name = None
|
||||
|
@ -13,3 +13,32 @@
|
||||
#
|
||||
# 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.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)
|
||||
|
@ -20,32 +20,6 @@ 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'),
|
||||
[wtforms.validators.Required(),
|
||||
wtforms.validators.Length(min=5, max=1024)])
|
||||
|
||||
|
||||
class ForgotPassForm(wtforms.Form):
|
||||
username = wtforms.TextField(
|
||||
_('Username or email'),
|
||||
@ -58,9 +32,6 @@ class ChangePassForm(wtforms.Form):
|
||||
'Password',
|
||||
[wtforms.validators.Required(),
|
||||
wtforms.validators.Length(min=5, max=1024)])
|
||||
userid = wtforms.HiddenField(
|
||||
'',
|
||||
[wtforms.validators.Required()])
|
||||
token = wtforms.HiddenField(
|
||||
'',
|
||||
[wtforms.validators.Required()])
|
||||
|
@ -14,19 +14,20 @@
|
||||
# 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 uuid
|
||||
import logging
|
||||
|
||||
import logging
|
||||
import wtforms
|
||||
from sqlalchemy import or_
|
||||
|
||||
from mediagoblin import mg_globals
|
||||
from mediagoblin.auth import lib as auth_lib
|
||||
from mediagoblin.db.models import User, Privilege
|
||||
from mediagoblin.tools.crypto import get_timed_signer_url
|
||||
from mediagoblin.db.models import User
|
||||
from mediagoblin.tools.mail import (normalize_email, send_email,
|
||||
email_debug_message)
|
||||
from mediagoblin.tools.template import render_template
|
||||
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__)
|
||||
|
||||
@ -62,11 +63,12 @@ def normalize_user_or_email_field(allow_email=True, allow_user=True):
|
||||
|
||||
|
||||
EMAIL_VERIFICATION_TEMPLATE = (
|
||||
u"http://{host}{uri}?"
|
||||
u"userid={userid}&token={verification_key}")
|
||||
u"{uri}?"
|
||||
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.
|
||||
|
||||
@ -74,19 +76,24 @@ def send_verification_email(user, request):
|
||||
- user: a user object
|
||||
- request: the request
|
||||
"""
|
||||
rendered_email = render_template(
|
||||
request, 'mediagoblin/auth/verification_email.txt',
|
||||
{'username': user.username,
|
||||
'verification_url': EMAIL_VERIFICATION_TEMPLATE.format(
|
||||
host=request.host,
|
||||
uri=request.urlgen('mediagoblin.auth.verify_email'),
|
||||
userid=unicode(user.id),
|
||||
verification_key=user.verification_key)})
|
||||
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(
|
||||
request, 'mediagoblin/auth/verification_email.txt',
|
||||
{'username': user.username,
|
||||
'verification_url': EMAIL_VERIFICATION_TEMPLATE.format(
|
||||
uri=request.urlgen('mediagoblin.auth.verify_email',
|
||||
qualified=True),
|
||||
verification_key=verification_key)})
|
||||
|
||||
# TODO: There is no error handling in place
|
||||
send_email(
|
||||
mg_globals.app_config['email_sender_address'],
|
||||
[user.email],
|
||||
[email],
|
||||
# TODO
|
||||
# Due to the distributed nature of GNU MediaGoblin, we should
|
||||
# find a way to send some additional information about the
|
||||
@ -96,11 +103,43 @@ def send_verification_email(user, request):
|
||||
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):
|
||||
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(
|
||||
email=register_form.data['email']).count()
|
||||
email=register_form.email.data).count()
|
||||
|
||||
extra_validation_passes = True
|
||||
|
||||
@ -118,17 +157,11 @@ def basic_extra_validation(register_form, *args):
|
||||
|
||||
def register_user(request, register_form):
|
||||
""" Handle user registration """
|
||||
extra_validation_passes = basic_extra_validation(register_form)
|
||||
extra_validation_passes = auth.extra_validation(register_form)
|
||||
|
||||
if extra_validation_passes:
|
||||
# Create the user
|
||||
user = User()
|
||||
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()
|
||||
user = auth.create_user(register_form)
|
||||
|
||||
# give the user the default privileges
|
||||
default_privileges = [
|
||||
@ -151,17 +184,37 @@ def register_user(request, register_form):
|
||||
return None
|
||||
|
||||
|
||||
def check_login_simple(username, password, username_might_be_email=False):
|
||||
search = (User.username == username)
|
||||
if username_might_be_email and ('@' in username):
|
||||
search = or_(search, User.email == username)
|
||||
user = User.query.filter(search).first()
|
||||
def check_login_simple(username, password):
|
||||
user = auth.get_user(username=username)
|
||||
if not user:
|
||||
_log.info("User %r not found", username)
|
||||
auth_lib.fake_login_attempt()
|
||||
hook_handle("auth_fake_login_attempt")
|
||||
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)
|
||||
return None
|
||||
_log.info("Logging %r in", username)
|
||||
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
|
||||
|
@ -14,36 +14,37 @@
|
||||
# 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 uuid
|
||||
import datetime
|
||||
from itsdangerous import BadSignature
|
||||
|
||||
from mediagoblin import messages, mg_globals
|
||||
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.translate import pass_to_ugettext as _
|
||||
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.lib import send_fp_verification_email
|
||||
from mediagoblin.auth.tools import (send_verification_email, register_user,
|
||||
send_fp_verification_email,
|
||||
check_login_simple)
|
||||
from mediagoblin import auth
|
||||
|
||||
|
||||
@allow_registration
|
||||
@auth_enabled
|
||||
def register(request):
|
||||
"""The registration view.
|
||||
|
||||
Note that usernames will always be lowercased. Email domains are lowercased while
|
||||
the first part remains case-sensitive.
|
||||
"""
|
||||
# Redirects to indexpage if registrations are disabled
|
||||
if not mg_globals.app_config["allow_registration"]:
|
||||
messages.add_message(
|
||||
request,
|
||||
messages.WARNING,
|
||||
_('Sorry, registration is disabled on this instance.'))
|
||||
return redirect(request, "index")
|
||||
if 'pass_auth' not in request.template_env.globals:
|
||||
redirect_name = hook_handle('auth_no_pass_redirect')
|
||||
return redirect(request, 'mediagoblin.plugins.{0}.register'.format(
|
||||
redirect_name))
|
||||
|
||||
register_form = auth_forms.RegistrationForm(request.form)
|
||||
register_form = hook_handle("auth_get_registration_form", request)
|
||||
|
||||
if request.method == 'POST' and register_form.validate():
|
||||
# TODO: Make sure the user doesn't exist already
|
||||
@ -59,28 +60,36 @@ def register(request):
|
||||
return render_to_response(
|
||||
request,
|
||||
'mediagoblin/auth/register.html',
|
||||
{'register_form': register_form})
|
||||
{'register_form': register_form,
|
||||
'post_url': request.urlgen('mediagoblin.auth.register')})
|
||||
|
||||
|
||||
@auth_enabled
|
||||
def login(request):
|
||||
"""
|
||||
MediaGoblin login 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
|
||||
|
||||
if request.method == 'POST':
|
||||
|
||||
username = login_form.data['username']
|
||||
username = login_form.username.data
|
||||
|
||||
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:
|
||||
# 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.save()
|
||||
|
||||
@ -97,6 +106,7 @@ def login(request):
|
||||
{'login_form': login_form,
|
||||
'next': request.GET.get('next') or request.form.get('next'),
|
||||
'login_failed': login_failed,
|
||||
'post_url': request.urlgen('mediagoblin.auth.login'),
|
||||
'allow_registration': mg_globals.app_config["allow_registration"]})
|
||||
|
||||
|
||||
@ -115,12 +125,26 @@ def verify_email(request):
|
||||
you are lucky :)
|
||||
"""
|
||||
# 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)
|
||||
|
||||
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.email_verified = True
|
||||
user.verification_key = None
|
||||
@ -169,9 +193,6 @@ def resend_activation(request):
|
||||
|
||||
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)
|
||||
send_verification_email(request.user, request)
|
||||
|
||||
@ -191,13 +212,16 @@ def forgot_password(request):
|
||||
Sends an email with an url to renew forgotten password.
|
||||
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,
|
||||
username=request.args.get('username'))
|
||||
|
||||
if not (request.method == 'POST' and fp_form.validate()):
|
||||
# Either GET request, or invalid form submitted. Display the template
|
||||
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
|
||||
# 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
|
||||
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)
|
||||
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
|
||||
formdata = _process_for_token(request)
|
||||
if not formdata['has_userid_and_token']:
|
||||
if not formdata['has_token']:
|
||||
return render_404(request)
|
||||
|
||||
formdata_token = formdata['vars']['token']
|
||||
formdata_userid = formdata['vars']['userid']
|
||||
formdata_vars = formdata['vars']
|
||||
|
||||
# check if it's a valid user id
|
||||
user = User.query.filter_by(id=formdata_userid).first()
|
||||
if not user:
|
||||
return render_404(request)
|
||||
# Catch error if token is faked or expired
|
||||
try:
|
||||
token = get_timed_signer_url("mail_verification_token") \
|
||||
.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
|
||||
if ((user and user.fp_verification_key and
|
||||
user.fp_verification_key == unicode(formdata_token) and
|
||||
datetime.datetime.now() < user.fp_token_expire
|
||||
and user.email_verified and user.status == 'active')):
|
||||
return redirect(
|
||||
request,
|
||||
'index')
|
||||
|
||||
# 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)
|
||||
|
||||
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)
|
||||
user.fp_verification_key = None
|
||||
user.fp_token_expire = None
|
||||
user.save()
|
||||
|
||||
messages.add_message(
|
||||
@ -293,12 +325,22 @@ def verify_forgot_password(request):
|
||||
return render_to_response(
|
||||
request,
|
||||
'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
|
||||
# or the token expired
|
||||
else:
|
||||
return render_404(request)
|
||||
if not user.email_verified:
|
||||
messages.add_message(
|
||||
request, messages.ERROR,
|
||||
_('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):
|
||||
@ -316,7 +358,6 @@ def _process_for_token(request):
|
||||
|
||||
formdata = {
|
||||
'vars': formdata_vars,
|
||||
'has_userid_and_token':
|
||||
'userid' in formdata_vars and 'token' in formdata_vars}
|
||||
'has_token': 'token' in formdata_vars}
|
||||
|
||||
return formdata
|
||||
|
@ -5,12 +5,13 @@ html_title = string(default="GNU MediaGoblin")
|
||||
# link to source for this MediaGoblin site
|
||||
source_link = string(default="https://gitorious.org/mediagoblin/mediagoblin")
|
||||
|
||||
# Enabled media types
|
||||
media_types = string_list(default=list("mediagoblin.media_types.image"))
|
||||
|
||||
# database stuff
|
||||
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
|
||||
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
|
||||
email_debug_mode = boolean(default=True)
|
||||
email_smtp_use_ssl = boolean(default=False)
|
||||
email_sender_address = string(default="notice@mediagoblin.example.org")
|
||||
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_pass = string(default=None)
|
||||
|
||||
|
@ -24,18 +24,6 @@ Session = scoped_session(sessionmaker())
|
||||
class GMGTableBase(object):
|
||||
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):
|
||||
return getattr(self, key)
|
||||
|
||||
|
@ -29,7 +29,7 @@ class MigrationManager(object):
|
||||
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):
|
||||
"""
|
||||
Args:
|
||||
@ -40,6 +40,7 @@ class MigrationManager(object):
|
||||
"""
|
||||
self.name = unicode(name)
|
||||
self.models = models
|
||||
self.foundations = foundations
|
||||
self.session = session
|
||||
self.migration_registry = migration_registry
|
||||
self._sorted_migrations = None
|
||||
@ -145,11 +146,11 @@ class MigrationManager(object):
|
||||
Create the table foundations (default rows) as layed out in FOUNDATIONS
|
||||
in mediagoblin.db.models
|
||||
"""
|
||||
from mediagoblin.db.models import FOUNDATIONS as MAIN_FOUNDATIONS
|
||||
for Model in MAIN_FOUNDATIONS.keys():
|
||||
for parameters in MAIN_FOUNDATIONS[Model]:
|
||||
row = Model(*parameters)
|
||||
row.save()
|
||||
for Model, rows in self.foundations.items():
|
||||
print u'\n + Laying foundations for %s table' % (Model.__name__)
|
||||
for parameters in rows:
|
||||
new_row = Model(**parameters)
|
||||
new_row.save()
|
||||
|
||||
def create_new_migration_record(self):
|
||||
"""
|
||||
@ -186,8 +187,7 @@ class MigrationManager(object):
|
||||
if self.name == u'__main__':
|
||||
return u"main mediagoblin tables"
|
||||
else:
|
||||
# TODO: Use the friendlier media manager "human readable" name
|
||||
return u'media type "%s"' % self.name
|
||||
return u'plugin "%s"' % self.name
|
||||
|
||||
def init_or_migrate(self):
|
||||
"""
|
||||
@ -214,10 +214,9 @@ class MigrationManager(object):
|
||||
|
||||
self.init_tables()
|
||||
# auto-set at latest migration number
|
||||
self.create_new_migration_record()
|
||||
if self.name==u'__main__':
|
||||
self.populate_table_foundations()
|
||||
|
||||
self.create_new_migration_record()
|
||||
self.populate_table_foundations()
|
||||
|
||||
self.printer(u"done.\n")
|
||||
self.set_current_migration()
|
||||
return u'inited'
|
||||
|
@ -289,10 +289,99 @@ def unique_collections_slug(db):
|
||||
|
||||
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'
|
||||
id = Column(Integer, primary_key=True)
|
||||
reporter_id = Column(Integer, ForeignKey(User.id), nullable=False)
|
||||
@ -302,7 +391,6 @@ class ReportBase_v0(declarative_base()):
|
||||
discriminator = Column('type', Unicode(50))
|
||||
__mapper_args__ = {'polymorphic_on': discriminator}
|
||||
|
||||
|
||||
class CommentReport_v0(ReportBase_v0):
|
||||
__tablename__ = 'core__reports_on_comments'
|
||||
__mapper_args__ = {'polymorphic_identity': 'comment_report'}
|
||||
@ -316,14 +404,13 @@ class MediaReport_v0(ReportBase_v0):
|
||||
__mapper_args__ = {'polymorphic_identity': 'media_report'}
|
||||
|
||||
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):
|
||||
__tablename__ = 'core__reports_archived'
|
||||
__mapper_args__ = {'polymorphic_identity': 'archived_report'}
|
||||
|
||||
id = Column('id',Integer, ForeignKey('core__reports.id'))
|
||||
|
||||
media_entry_id = Column(Integer, ForeignKey(MediaEntry.id))
|
||||
comment_id = Column(Integer, ForeignKey(MediaComment.id))
|
||||
resolver_id = Column(Integer, ForeignKey(User.id), nullable=False)
|
||||
@ -344,7 +431,6 @@ class Privilege_v0(declarative_base()):
|
||||
|
||||
class PrivilegeUserAssociation_v0(declarative_base()):
|
||||
__tablename__ = 'core__privileges_users'
|
||||
|
||||
group_id = Column(
|
||||
'core__privilege_id',
|
||||
Integer,
|
||||
@ -356,7 +442,7 @@ class PrivilegeUserAssociation_v0(declarative_base()):
|
||||
ForeignKey(Privilege.id),
|
||||
primary_key=True)
|
||||
|
||||
@RegisterMigration(11, MIGRATIONS)
|
||||
@RegisterMigration(14, MIGRATIONS)
|
||||
def create_moderation_tables(db):
|
||||
ReportBase_v0.__table__.create(db.bind)
|
||||
CommentReport_v0.__table__.create(db.bind)
|
||||
|
@ -29,13 +29,14 @@ real objects.
|
||||
|
||||
import uuid
|
||||
import re
|
||||
import datetime
|
||||
from datetime import datetime
|
||||
|
||||
from werkzeug.utils import cached_property
|
||||
|
||||
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.pluginapi import hook_handle
|
||||
from mediagoblin.tools.text import cleaned_markdown_conversion
|
||||
from mediagoblin.tools.url import slugify
|
||||
|
||||
@ -201,14 +202,14 @@ class MediaEntryMixin(GenerateSlugMixin):
|
||||
|
||||
Raises FileTypeNotSupported in case no such manager is enabled
|
||||
"""
|
||||
# TODO, we should be able to make this a simple lookup rather
|
||||
# than iterating through all media managers.
|
||||
for media_type, manager in get_media_managers():
|
||||
if media_type == self.media_type:
|
||||
return manager(self)
|
||||
manager = hook_handle(('media_manager', self.media_type))
|
||||
if manager:
|
||||
return manager(self)
|
||||
|
||||
# Not found? Then raise an error
|
||||
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):
|
||||
"""
|
||||
@ -287,6 +288,13 @@ class MediaCommentMixin(object):
|
||||
"""
|
||||
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):
|
||||
def check_slug_used(self, slug):
|
||||
|
@ -24,16 +24,16 @@ import datetime
|
||||
from sqlalchemy import Column, Integer, Unicode, UnicodeText, DateTime, \
|
||||
Boolean, ForeignKey, UniqueConstraint, PrimaryKeyConstraint, \
|
||||
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.sql.expression import desc
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
from sqlalchemy.util import memoized_property
|
||||
from sqlalchemy.schema import Table
|
||||
|
||||
from mediagoblin.db.extratypes import PathTupleWithSlashes, JSONEncoded
|
||||
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.common import import_component
|
||||
|
||||
@ -239,8 +239,8 @@ class MediaEntry(Base, MediaEntryMixin):
|
||||
This will *not* automatically delete unused collections, which
|
||||
can remain empty...
|
||||
|
||||
:keyword del_orphan_tags: True/false if we delete unused Tags too
|
||||
:keyword commit: True/False if this should end the db transaction"""
|
||||
:param del_orphan_tags: True/false if we delete unused Tags too
|
||||
:param commit: True/False if this should end the db transaction"""
|
||||
# User's CollectionItems are automatically deleted via "cascade".
|
||||
# Comments on this Media are deleted by cascade, hopefully.
|
||||
|
||||
@ -393,6 +393,10 @@ class MediaComment(Base, MediaCommentMixin):
|
||||
backref=backref("posted_comments",
|
||||
lazy="dynamic",
|
||||
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.
|
||||
# So do the full thing.
|
||||
@ -484,6 +488,92 @@ class ProcessingMetaData(Base):
|
||||
"""A dict like view on this object"""
|
||||
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):
|
||||
"""
|
||||
@ -672,20 +762,38 @@ class PrivilegeUserAssociation(Base):
|
||||
ForeignKey(Privilege.id),
|
||||
primary_key=True)
|
||||
|
||||
with_polymorphic(
|
||||
Notification,
|
||||
[ProcessingNotification, CommentNotification])
|
||||
|
||||
privilege_foundations = [[u'admin'], [u'moderator'], [u'uploader'],[u'reporter'], [u'commenter'] ,[u'active']]
|
||||
|
||||
MODELS = [
|
||||
User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem,
|
||||
MediaFile, FileKeynames, MediaAttachmentFile, ProcessingMetaData, ReportBase,
|
||||
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
|
||||
# ModelObject:List of Rows
|
||||
# (Each Row must be a list of parameters that can create and instance of the ModelObject)
|
||||
#
|
||||
"""
|
||||
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:
|
||||
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}
|
||||
|
||||
######################################################
|
||||
|
@ -18,6 +18,29 @@
|
||||
TODO: indexes on foreignkeys, where useful.
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
# WHAT IS THIS FILE?
|
||||
# ------------------
|
||||
#
|
||||
# Upon occasion, someone runs into this file and wonders why we have
|
||||
# both a models.py and a models_v0.py.
|
||||
#
|
||||
# The short of it is: you can ignore this file.
|
||||
#
|
||||
# The long version is, in two parts:
|
||||
#
|
||||
# - We used to use MongoDB, then we switched to SQL and SQLAlchemy.
|
||||
# We needed to convert peoples' databases; the script we had would
|
||||
# switch them to the first version right after Mongo, convert over
|
||||
# all their tables, then run any migrations that were added after.
|
||||
#
|
||||
# - That script is now removed, but there is some discussion of
|
||||
# writing a test that would set us at the first SQL migration and
|
||||
# run everything after. If we wrote that, this file would still be
|
||||
# useful. But for now, it's legacy!
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
|
||||
import datetime
|
||||
import sys
|
||||
|
@ -52,10 +52,6 @@ class DatabaseMaster(object):
|
||||
def load_models(app_config):
|
||||
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():
|
||||
_log.debug("Loading %s.models", plugin)
|
||||
try:
|
||||
|
@ -25,7 +25,7 @@ from mediagoblin.db.models import (MediaEntry, Tag, MediaTag, Collection, \
|
||||
|
||||
|
||||
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)
|
||||
Session.commit()
|
||||
|
||||
|
@ -21,9 +21,11 @@ from werkzeug.exceptions import Forbidden, NotFound
|
||||
from werkzeug.urls import url_quote
|
||||
|
||||
from mediagoblin import mg_globals as mgg
|
||||
from mediagoblin.db.models import MediaEntry, User, MediaComment, Privilege, \
|
||||
UserBan
|
||||
from mediagoblin.tools.response import redirect, render_404, render_user_banned
|
||||
from mediagoblin import messages
|
||||
from mediagoblin.db.models import MediaEntry, User, MediaComment,
|
||||
UserBan
|
||||
from mediagoblin.tools.response import redirect, render_404
|
||||
from mediagoblin.tools.translate import pass_to_ugettext as _
|
||||
|
||||
|
||||
def require_active_login(controller):
|
||||
@ -107,8 +109,8 @@ def user_may_alter_collection(controller):
|
||||
"""
|
||||
@wraps(controller)
|
||||
def wrapper(request, *args, **kwargs):
|
||||
creator_id = request.db.User.find_one(
|
||||
{'username': request.matchdict['user']}).id
|
||||
creator_id = request.db.User.query.filter_by(
|
||||
username=request.matchdict['user']).first().id
|
||||
if not (request.user.is_admin or
|
||||
request.user.id == creator_id):
|
||||
raise Forbidden()
|
||||
@ -182,15 +184,15 @@ def get_user_collection(controller):
|
||||
"""
|
||||
@wraps(controller)
|
||||
def wrapper(request, *args, **kwargs):
|
||||
user = request.db.User.find_one(
|
||||
{'username': request.matchdict['user']})
|
||||
user = request.db.User.query.filter_by(
|
||||
username=request.matchdict['user']).first()
|
||||
|
||||
if not user:
|
||||
return render_404(request)
|
||||
|
||||
collection = request.db.Collection.find_one(
|
||||
{'slug': request.matchdict['collection'],
|
||||
'creator': user.id})
|
||||
collection = request.db.Collection.query.filter_by(
|
||||
slug=request.matchdict['collection'],
|
||||
creator=user.id).first()
|
||||
|
||||
# Still no collection? Okay, 404.
|
||||
if not collection:
|
||||
@ -207,14 +209,14 @@ def get_user_collection_item(controller):
|
||||
"""
|
||||
@wraps(controller)
|
||||
def wrapper(request, *args, **kwargs):
|
||||
user = request.db.User.find_one(
|
||||
{'username': request.matchdict['user']})
|
||||
user = request.db.User.query.filter_by(
|
||||
username=request.matchdict['user']).first()
|
||||
|
||||
if not user:
|
||||
return render_404(request)
|
||||
|
||||
collection_item = request.db.CollectionItem.find_one(
|
||||
{'id': request.matchdict['collection_item'] })
|
||||
collection_item = request.db.CollectionItem.query.filter_by(
|
||||
id=request.matchdict['collection_item']).first()
|
||||
|
||||
# Still no collection item? Okay, 404.
|
||||
if not collection_item:
|
||||
@ -247,6 +249,21 @@ def get_media_entry_by_id(controller):
|
||||
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):
|
||||
"""
|
||||
Pass in a MediaComment based off of a url component
|
||||
@ -264,15 +281,26 @@ def get_media_comment_by_id(controller):
|
||||
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):
|
||||
"""Decorator, passing in a workbench as kwarg which is cleaned up afterwards"""
|
||||
|
||||
@wraps(func)
|
||||
def new_func(*args, **kwargs):
|
||||
with mgg.workbench_manager.create() as workbench:
|
||||
return func(*args, workbench=workbench, **kwargs)
|
||||
|
||||
return new_func
|
||||
|
||||
def require_admin_or_moderator_login(controller):
|
||||
|
@ -16,9 +16,11 @@
|
||||
|
||||
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.licenses import licenses_as_choices
|
||||
from mediagoblin.auth.forms import normalize_user_or_email_field
|
||||
|
||||
|
||||
class EditForm(wtforms.Form):
|
||||
title = wtforms.TextField(
|
||||
@ -59,6 +61,12 @@ class EditProfileForm(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'),
|
||||
[
|
||||
@ -67,8 +75,6 @@ class EditAccountForm(wtforms.Form):
|
||||
],
|
||||
choices=licenses_as_choices(),
|
||||
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):
|
||||
|
@ -26,3 +26,5 @@ add_route('mediagoblin.edit.delete_account', '/edit/account/delete/',
|
||||
'mediagoblin.edit.views:delete_account')
|
||||
add_route('mediagoblin.edit.pass', '/edit/password/',
|
||||
'mediagoblin.edit.views:change_pass')
|
||||
add_route('mediagoblin.edit.verify_email', '/edit/verify_email/',
|
||||
'mediagoblin.edit.views:verify_email')
|
||||
|
@ -16,25 +16,31 @@
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from itsdangerous import BadSignature
|
||||
from werkzeug.exceptions import Forbidden
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from mediagoblin import messages
|
||||
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.lib import may_edit_media
|
||||
from mediagoblin.decorators import (require_active_login, active_user_from_url,
|
||||
get_media_entry_by_id,
|
||||
user_may_alter_collection, get_user_collection)
|
||||
from mediagoblin.tools.response import render_to_response, \
|
||||
redirect, redirect_obj
|
||||
get_media_entry_by_id, user_may_alter_collection,
|
||||
get_user_collection)
|
||||
from mediagoblin.tools.crypto import get_timed_signer_url
|
||||
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.template import render_template
|
||||
from mediagoblin.tools.text import (
|
||||
convert_to_tag_list_of_dicts, media_tags_as_string)
|
||||
from mediagoblin.tools.url import slugify
|
||||
from mediagoblin.db.util import check_media_slug_used, check_collection_slug_used
|
||||
from mediagoblin.db.models import User
|
||||
|
||||
import mimetypes
|
||||
|
||||
@ -212,6 +218,10 @@ def edit_profile(request, url_user=None):
|
||||
{'user': user,
|
||||
'form': form})
|
||||
|
||||
EMAIL_VERIFICATION_TEMPLATE = (
|
||||
u'{uri}?'
|
||||
u'token={verification_key}')
|
||||
|
||||
|
||||
@require_active_login
|
||||
def edit_account(request):
|
||||
@ -220,27 +230,22 @@ def edit_account(request):
|
||||
wants_comment_notification=user.wants_comment_notification,
|
||||
license_preference=user.license_preference)
|
||||
|
||||
if request.method == 'POST':
|
||||
form_validated = form.validate()
|
||||
if request.method == 'POST' and form.validate():
|
||||
user.wants_comment_notification = form.wants_comment_notification.data
|
||||
|
||||
if form_validated and \
|
||||
form.wants_comment_notification.validate(form):
|
||||
user.wants_comment_notification = \
|
||||
form.wants_comment_notification.data
|
||||
user.license_preference = form.license_preference.data
|
||||
|
||||
if form_validated and \
|
||||
form.license_preference.validate(form):
|
||||
user.license_preference = \
|
||||
form.license_preference.data
|
||||
if form.new_email.data:
|
||||
_update_email(request, form, user)
|
||||
|
||||
if form_validated and not form.errors:
|
||||
if not form.errors:
|
||||
user.save()
|
||||
messages.add_message(request,
|
||||
messages.SUCCESS,
|
||||
_("Account settings saved"))
|
||||
messages.SUCCESS,
|
||||
_("Account settings saved"))
|
||||
return redirect(request,
|
||||
'mediagoblin.user_pages.user_home',
|
||||
user=user.username)
|
||||
'mediagoblin.user_pages.user_home',
|
||||
user=user.username)
|
||||
|
||||
return render_to_response(
|
||||
request,
|
||||
@ -300,9 +305,9 @@ def edit_collection(request, collection):
|
||||
form.slug.data, collection.id)
|
||||
|
||||
# Make sure there isn't already a Collection with this title
|
||||
existing_collection = request.db.Collection.find_one({
|
||||
'creator': request.user.id,
|
||||
'title':form.title.data})
|
||||
existing_collection = request.db.Collection.query.filter_by(
|
||||
creator=request.user.id,
|
||||
title=form.title.data).first()
|
||||
|
||||
if existing_collection and existing_collection.id != collection.id:
|
||||
messages.add_message(
|
||||
@ -337,12 +342,16 @@ def edit_collection(request, collection):
|
||||
|
||||
@require_active_login
|
||||
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)
|
||||
user = request.user
|
||||
|
||||
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.errors.append(
|
||||
_('Wrong password'))
|
||||
@ -354,7 +363,7 @@ def change_pass(request):
|
||||
'user': user})
|
||||
|
||||
# Password matches
|
||||
user.pw_hash = auth_lib.bcrypt_gen_password_hash(
|
||||
user.pw_hash = auth.gen_password_hash(
|
||||
form.new_password.data)
|
||||
user.save()
|
||||
|
||||
@ -369,3 +378,77 @@ def change_pass(request):
|
||||
'mediagoblin/edit/change_pass.html',
|
||||
{'form': form,
|
||||
'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)
|
||||
|
@ -32,17 +32,18 @@ def dbupdate_parse_setup(subparser):
|
||||
|
||||
|
||||
class DatabaseData(object):
|
||||
def __init__(self, name, models, migrations):
|
||||
def __init__(self, name, models, foundations, migrations):
|
||||
self.name = name
|
||||
self.models = models
|
||||
self.foundations = foundations
|
||||
self.migrations = migrations
|
||||
|
||||
def make_migration_manager(self, session):
|
||||
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
|
||||
installed so we can do migrations and table initialization.
|
||||
@ -54,17 +55,11 @@ def gather_database_data(media_types, plugins):
|
||||
# Add main first
|
||||
from mediagoblin.db.models import MODELS as MAIN_MODELS
|
||||
from mediagoblin.db.migrations import MIGRATIONS as MAIN_MIGRATIONS
|
||||
from mediagoblin.db.models import FOUNDATIONS as MAIN_FOUNDATIONS
|
||||
|
||||
managed_dbdata.append(
|
||||
DatabaseData(
|
||||
u'__main__', MAIN_MODELS, MAIN_MIGRATIONS))
|
||||
|
||||
# Then get all registered media managers (eventually, plugins)
|
||||
for media_type in media_types:
|
||||
models = import_component('%s.models:MODELS' % media_type)
|
||||
migrations = import_component('%s.migrations:MIGRATIONS' % media_type)
|
||||
managed_dbdata.append(
|
||||
DatabaseData(media_type, models, migrations))
|
||||
u'__main__', MAIN_MODELS, MAIN_FOUNDATIONS, MAIN_MIGRATIONS))
|
||||
|
||||
for plugin in plugins:
|
||||
try:
|
||||
@ -90,13 +85,26 @@ forgotten to add it? ({1})'.format(plugin, exc))
|
||||
|
||||
migrations = {}
|
||||
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))
|
||||
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:
|
||||
managed_dbdata.append(
|
||||
DatabaseData(plugin, models, migrations))
|
||||
DatabaseData(plugin, models, foundations, migrations))
|
||||
|
||||
|
||||
return managed_dbdata
|
||||
@ -110,13 +118,24 @@ def run_dbupdate(app_config, global_config):
|
||||
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
|
||||
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)
|
||||
|
||||
|
@ -63,7 +63,7 @@ def _import_media(db, args):
|
||||
# TODO: Add import of queue files
|
||||
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():
|
||||
_log.info('Importing: {0} - {1}'.format(
|
||||
entry.title.encode('ascii', 'replace'),
|
||||
@ -204,7 +204,7 @@ def _export_media(db, args):
|
||||
# TODO: Add export of queue files
|
||||
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():
|
||||
_log.info(u'Exporting {0} - {1}'.format(
|
||||
entry.title,
|
||||
|
@ -15,7 +15,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
|
||||
def adduser_parser_setup(subparser):
|
||||
@ -40,9 +40,9 @@ def adduser(args):
|
||||
|
||||
db = mg_globals.database
|
||||
users_with_username = \
|
||||
db.User.find({
|
||||
'username': args.username.lower(),
|
||||
}).count()
|
||||
db.User.query.filter_by(
|
||||
username=args.username.lower()
|
||||
).count()
|
||||
|
||||
if users_with_username:
|
||||
print u'Sorry, a user with that name already exists.'
|
||||
@ -52,7 +52,7 @@ def adduser(args):
|
||||
entry = db.User()
|
||||
entry.username = unicode(args.username.lower())
|
||||
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.email_verified = True
|
||||
default_privileges = [
|
||||
@ -78,7 +78,8 @@ def makeadmin(args):
|
||||
|
||||
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:
|
||||
user.is_admin = True
|
||||
user.all_privileges.append(
|
||||
@ -105,9 +106,10 @@ def changepw(args):
|
||||
|
||||
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:
|
||||
user.pw_hash = auth_lib.bcrypt_gen_password_hash(args.password)
|
||||
user.pw_hash = auth.gen_password_hash(args.password)
|
||||
user.save()
|
||||
print 'Password successfully changed'
|
||||
else:
|
||||
|
Binary file not shown.
@ -15,15 +15,15 @@
|
||||
# Elrond <elrond+mediagoblin.org@samba-tng.org>, 2011
|
||||
# Art O. Pal <artopal@fastmail.fm>, 2011
|
||||
# spaetz <sebastian@sspaeth.de>, 2012
|
||||
# Vinzenz Vietzke <vietzke@b1-systems.de>, 2012
|
||||
# Vinzenz Vietzke <vietzke@b1-systems.de>, 2011
|
||||
# Vinzenz Vietzke <vinz@vinzv.de>, 2012
|
||||
# Vinzenz Vietzke <vinz@vinzv.de>, 2011
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: GNU MediaGoblin\n"
|
||||
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
|
||||
"POT-Creation-Date: 2013-05-27 13:54-0500\n"
|
||||
"PO-Revision-Date: 2013-05-27 18:54+0000\n"
|
||||
"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
|
||||
"PO-Revision-Date: 2013-05-28 10:43+0000\n"
|
||||
"Last-Translator: Elrond <elrond+mediagoblin.org@samba-tng.org>\n"
|
||||
"Language-Team: German (http://www.transifex.com/projects/p/mediagoblin/language/de/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -47,7 +47,7 @@ msgstr "E-Mail-Adresse"
|
||||
|
||||
#: mediagoblin/auth/forms.py:41
|
||||
msgid "Username or Email"
|
||||
msgstr ""
|
||||
msgstr "Benutzername oder E-Mail-Adresse"
|
||||
|
||||
#: mediagoblin/auth/forms.py:52
|
||||
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"
|
||||
" or\n"
|
||||
" <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/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
|
||||
msgid "PDF file"
|
||||
msgstr ""
|
||||
msgstr "PDF-Datei"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
|
||||
msgid "Toggle Rotate"
|
||||
@ -944,11 +944,11 @@ msgstr ""
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
|
||||
msgid "Added"
|
||||
msgstr ""
|
||||
msgstr "Hinzugefügt"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
|
||||
msgid "Created"
|
||||
msgstr ""
|
||||
msgstr "Originaldatum"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
|
||||
#: 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
|
||||
msgid "year"
|
||||
msgstr ""
|
||||
msgstr "Jahr"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:63
|
||||
msgid "month"
|
||||
msgstr ""
|
||||
msgstr "Monat"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:64
|
||||
msgid "week"
|
||||
msgstr ""
|
||||
msgstr "Woche"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:65
|
||||
msgid "day"
|
||||
msgstr ""
|
||||
msgstr "Tag"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:66
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
msgstr "Stunde"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:67
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
msgstr "Minute"
|
||||
|
||||
#: mediagoblin/user_pages/forms.py:23
|
||||
msgid "Comment"
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\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"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -17,94 +17,94 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 0.9.6\n"
|
||||
|
||||
#: mediagoblin/auth/forms.py:26
|
||||
#: mediagoblin/auth/forms.py:25
|
||||
msgid "Username"
|
||||
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
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
#: mediagoblin/auth/forms.py:34
|
||||
#: mediagoblin/auth/forms.py:33
|
||||
msgid "Email address"
|
||||
msgstr ""
|
||||
|
||||
#: mediagoblin/auth/forms.py:41
|
||||
#: mediagoblin/auth/forms.py:40
|
||||
msgid "Username or Email"
|
||||
msgstr ""
|
||||
|
||||
#: mediagoblin/auth/forms.py:52
|
||||
#: mediagoblin/auth/forms.py:51
|
||||
msgid "Username or email"
|
||||
msgstr ""
|
||||
|
||||
#: mediagoblin/auth/tools.py:31
|
||||
#: mediagoblin/auth/tools.py:42
|
||||
msgid "Invalid User name or email address."
|
||||
msgstr ""
|
||||
|
||||
#: mediagoblin/auth/tools.py:32
|
||||
#: mediagoblin/auth/tools.py:43
|
||||
msgid "This field does not take email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: mediagoblin/auth/tools.py:33
|
||||
#: mediagoblin/auth/tools.py:44
|
||||
msgid "This field requires an email address."
|
||||
msgstr ""
|
||||
|
||||
#: mediagoblin/auth/views.py:54
|
||||
msgid "Sorry, registration is disabled on this instance."
|
||||
msgstr ""
|
||||
|
||||
#: mediagoblin/auth/views.py:68
|
||||
#: mediagoblin/auth/tools.py:109
|
||||
msgid "Sorry, a user with that name already exists."
|
||||
msgstr ""
|
||||
|
||||
#: mediagoblin/auth/views.py:72
|
||||
#: mediagoblin/auth/tools.py:113
|
||||
msgid "Sorry, a user with that email address already exists."
|
||||
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 ""
|
||||
"Your email address has been verified. You may now login, edit your "
|
||||
"profile, and submit images!"
|
||||
msgstr ""
|
||||
|
||||
#: mediagoblin/auth/views.py:188
|
||||
#: mediagoblin/auth/views.py:139
|
||||
msgid "The verification key or user id is incorrect"
|
||||
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!"
|
||||
msgstr ""
|
||||
|
||||
#: mediagoblin/auth/views.py:214
|
||||
#: mediagoblin/auth/views.py:165
|
||||
msgid "You've already verified your email address!"
|
||||
msgstr ""
|
||||
|
||||
#: mediagoblin/auth/views.py:227
|
||||
#: mediagoblin/auth/views.py:178
|
||||
msgid "Resent your verification email."
|
||||
msgstr ""
|
||||
|
||||
#: mediagoblin/auth/views.py:258
|
||||
#: mediagoblin/auth/views.py:209
|
||||
msgid ""
|
||||
"If that email address (case sensitive!) is registered an email has been "
|
||||
"sent with instructions on how to change your password."
|
||||
msgstr ""
|
||||
|
||||
#: mediagoblin/auth/views.py:269
|
||||
#: mediagoblin/auth/views.py:220
|
||||
msgid "Couldn't find someone with that username."
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
#: mediagoblin/auth/views.py:279
|
||||
#: mediagoblin/auth/views.py:230
|
||||
msgid ""
|
||||
"Could not send password recovery email as your username is inactive or "
|
||||
"your account's email address has not been verified."
|
||||
msgstr ""
|
||||
|
||||
#: mediagoblin/auth/views.py:336
|
||||
#: mediagoblin/auth/views.py:287
|
||||
msgid "You can now log in using your new password."
|
||||
msgstr ""
|
||||
|
||||
@ -634,13 +634,13 @@ msgid "Editing attachments for %(media_title)s"
|
||||
msgstr ""
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:171
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:187
|
||||
msgid "Attachments"
|
||||
msgstr ""
|
||||
|
||||
#: 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"
|
||||
msgstr ""
|
||||
|
||||
@ -763,6 +763,17 @@ msgstr ""
|
||||
msgid "WebM file (Vorbis codec)"
|
||||
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/stl.html:87
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
|
||||
@ -928,21 +939,10 @@ msgstr ""
|
||||
msgid "Add this comment"
|
||||
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
|
||||
msgid "Added"
|
||||
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:40
|
||||
#, python-format
|
||||
|
Binary file not shown.
@ -12,8 +12,8 @@ msgstr ""
|
||||
"Project-Id-Version: GNU MediaGoblin\n"
|
||||
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
|
||||
"POT-Creation-Date: 2013-05-27 13:54-0500\n"
|
||||
"PO-Revision-Date: 2013-05-27 18:54+0000\n"
|
||||
"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
|
||||
"PO-Revision-Date: 2013-06-01 21:16+0000\n"
|
||||
"Last-Translator: aleksejrs <deletesoftware@yandex.ru>\n"
|
||||
"Language-Team: Esperanto (http://www.transifex.com/projects/p/mediagoblin/language/eo/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -37,7 +37,7 @@ msgstr "Retpoŝtadreso"
|
||||
|
||||
#: mediagoblin/auth/forms.py:41
|
||||
msgid "Username or Email"
|
||||
msgstr ""
|
||||
msgstr "Uzantonomo aŭ retpoŝtadreso"
|
||||
|
||||
#: mediagoblin/auth/forms.py:52
|
||||
msgid "Username or email"
|
||||
@ -178,7 +178,7 @@ msgstr "Permesila prefero"
|
||||
|
||||
#: mediagoblin/edit/forms.py:69
|
||||
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
|
||||
msgid "Email me when others comment on my media"
|
||||
@ -264,7 +264,7 @@ msgstr "Malĝusta pasvorto"
|
||||
|
||||
#: mediagoblin/edit/views.py:363
|
||||
msgid "Your password was changed successfully"
|
||||
msgstr ""
|
||||
msgstr "Via pasvorto estas sukcese ŝanĝita"
|
||||
|
||||
#: mediagoblin/gmg_commands/assetlink.py:60
|
||||
msgid "Cannot link theme... no theme set\n"
|
||||
@ -665,11 +665,11 @@ msgstr "Konservi ŝanĝojn"
|
||||
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
|
||||
#, python-format
|
||||
msgid "Changing %(username)s's password"
|
||||
msgstr ""
|
||||
msgstr "Ŝanĝado de pasvorto de %(username)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
msgstr "Konservi"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
|
||||
#, python-format
|
||||
@ -700,7 +700,7 @@ msgstr "Ŝanĝado de kontagordoj de %(username)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
|
||||
msgid "Change your password."
|
||||
msgstr ""
|
||||
msgstr "Ŝanĝi la pasvorton"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
|
||||
msgid "Delete my account"
|
||||
@ -774,7 +774,7 @@ msgstr "Bildo de «%(media_title)s»"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
|
||||
msgid "PDF file"
|
||||
msgstr ""
|
||||
msgstr "PDF-dosiero"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
|
||||
msgid "Toggle Rotate"
|
||||
@ -930,15 +930,15 @@ msgstr "Aldoni ĉi tiun komenton"
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
|
||||
#, python-format
|
||||
msgid "%(formatted_time)s ago"
|
||||
msgstr ""
|
||||
msgstr "antaŭ %(formatted_time)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
|
||||
msgid "Added"
|
||||
msgstr ""
|
||||
msgstr "Aldonita"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
|
||||
msgid "Created"
|
||||
msgstr ""
|
||||
msgstr "Kreita"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
|
||||
@ -1126,27 +1126,27 @@ msgstr ""
|
||||
|
||||
#: mediagoblin/tools/timesince.py:62
|
||||
msgid "year"
|
||||
msgstr ""
|
||||
msgstr "jaro(j)"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:63
|
||||
msgid "month"
|
||||
msgstr ""
|
||||
msgstr "monato(j)"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:64
|
||||
msgid "week"
|
||||
msgstr ""
|
||||
msgstr "semajno(j)"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:65
|
||||
msgid "day"
|
||||
msgstr ""
|
||||
msgstr "tago(j)"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:66
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
msgstr "horo(j)"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:67
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
msgstr "minuto(j)"
|
||||
|
||||
#: mediagoblin/user_pages/forms.py:23
|
||||
msgid "Comment"
|
||||
@ -1185,7 +1185,7 @@ msgstr "komentis je via afiŝo"
|
||||
|
||||
#: mediagoblin/user_pages/views.py:169
|
||||
msgid "Sorry, comments are disabled."
|
||||
msgstr ""
|
||||
msgstr "Ve, komentado estas malebligita."
|
||||
|
||||
#: mediagoblin/user_pages/views.py:174
|
||||
msgid "Oops, your comment was empty."
|
||||
|
Binary file not shown.
@ -20,8 +20,8 @@ msgstr ""
|
||||
"Project-Id-Version: GNU MediaGoblin\n"
|
||||
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
|
||||
"POT-Creation-Date: 2013-05-27 13:54-0500\n"
|
||||
"PO-Revision-Date: 2013-05-27 18:54+0000\n"
|
||||
"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
|
||||
"PO-Revision-Date: 2013-06-02 21:23+0000\n"
|
||||
"Last-Translator: larjona <larjona99@gmail.com>\n"
|
||||
"Language-Team: Spanish (http://www.transifex.com/projects/p/mediagoblin/language/es/)\n"
|
||||
"MIME-Version: 1.0\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
|
||||
msgid "Username or Email"
|
||||
msgstr ""
|
||||
msgstr "Nombre de usuario o correo electrónico"
|
||||
|
||||
#: mediagoblin/auth/forms.py:52
|
||||
msgid "Username or email"
|
||||
@ -272,7 +272,7 @@ msgstr "Contraseña incorrecta"
|
||||
|
||||
#: mediagoblin/edit/views.py:363
|
||||
msgid "Your password was changed successfully"
|
||||
msgstr ""
|
||||
msgstr "Se ha cambiado la contraseña correctamente"
|
||||
|
||||
#: mediagoblin/gmg_commands/assetlink.py:60
|
||||
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
|
||||
#, python-format
|
||||
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
|
||||
#, python-format
|
||||
msgid "Skipping \"%s\"; already set up.\n"
|
||||
msgstr ""
|
||||
msgstr "Omitiendo \"%s\"; ya está establecido.\n"
|
||||
|
||||
#: mediagoblin/gmg_commands/assetlink.py:124
|
||||
#, python-format
|
||||
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
|
||||
msgid ""
|
||||
@ -315,7 +315,7 @@ msgstr "Lo sentidos, No soportamos ese tipo de archivo :("
|
||||
|
||||
#: mediagoblin/media_types/pdf/processing.py:136
|
||||
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
|
||||
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"
|
||||
" or\n"
|
||||
" <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/themes/airy/templates/mediagoblin/bits/logo.html:23
|
||||
@ -673,11 +673,11 @@ msgstr "Guardar cambios"
|
||||
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
|
||||
#, python-format
|
||||
msgid "Changing %(username)s's password"
|
||||
msgstr ""
|
||||
msgstr "Cambiando la contraseña de %(username)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
msgstr "Guardar"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
|
||||
#, 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
|
||||
msgid "Change your password."
|
||||
msgstr ""
|
||||
msgstr "Cambiar tu contraseña."
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
|
||||
msgid "Delete my account"
|
||||
@ -782,7 +782,7 @@ msgstr "Imágenes para %(media_title)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
|
||||
msgid "PDF file"
|
||||
msgstr ""
|
||||
msgstr "Fichero PDF"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
|
||||
msgid "Toggle Rotate"
|
||||
@ -938,15 +938,15 @@ msgstr "Añade un comentario "
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
|
||||
#, python-format
|
||||
msgid "%(formatted_time)s ago"
|
||||
msgstr ""
|
||||
msgstr "hace %(formatted_time)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
|
||||
msgid "Added"
|
||||
msgstr ""
|
||||
msgstr "Agregado"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
|
||||
msgid "Created"
|
||||
msgstr ""
|
||||
msgstr "Creado"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
|
||||
#: 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
|
||||
msgid "year"
|
||||
msgstr ""
|
||||
msgstr "año"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:63
|
||||
msgid "month"
|
||||
msgstr ""
|
||||
msgstr "mes"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:64
|
||||
msgid "week"
|
||||
msgstr ""
|
||||
msgstr "semana"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:65
|
||||
msgid "day"
|
||||
msgstr ""
|
||||
msgstr "día"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:66
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
msgstr "hora"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:67
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
msgstr "minuto"
|
||||
|
||||
#: mediagoblin/user_pages/forms.py:23
|
||||
msgid "Comment"
|
||||
@ -1193,7 +1193,7 @@ msgstr "comentó tu publicación"
|
||||
|
||||
#: mediagoblin/user_pages/views.py:169
|
||||
msgid "Sorry, comments are disabled."
|
||||
msgstr ""
|
||||
msgstr "Lo siento, los comentarios están desactivados."
|
||||
|
||||
#: mediagoblin/user_pages/views.py:174
|
||||
msgid "Oops, your comment was empty."
|
||||
|
Binary file not shown.
@ -11,8 +11,8 @@ msgstr ""
|
||||
"Project-Id-Version: GNU MediaGoblin\n"
|
||||
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
|
||||
"POT-Creation-Date: 2013-05-27 13:54-0500\n"
|
||||
"PO-Revision-Date: 2013-05-27 18:54+0000\n"
|
||||
"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
|
||||
"PO-Revision-Date: 2013-06-01 07:11+0000\n"
|
||||
"Last-Translator: GenghisKhan <genghiskhan@gmx.ca>\n"
|
||||
"Language-Team: Hebrew (http://www.transifex.com/projects/p/mediagoblin/language/he/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -36,7 +36,7 @@ msgstr "כתובת דוא״ל"
|
||||
|
||||
#: mediagoblin/auth/forms.py:41
|
||||
msgid "Username or Email"
|
||||
msgstr ""
|
||||
msgstr "שם משתמש או דוא״ל"
|
||||
|
||||
#: mediagoblin/auth/forms.py:52
|
||||
msgid "Username or email"
|
||||
@ -263,7 +263,7 @@ msgstr "סיסמה שגויה"
|
||||
|
||||
#: mediagoblin/edit/views.py:363
|
||||
msgid "Your password was changed successfully"
|
||||
msgstr ""
|
||||
msgstr "סיסמתך שונתה בהצלחה"
|
||||
|
||||
#: mediagoblin/gmg_commands/assetlink.py:60
|
||||
msgid "Cannot link theme... no theme set\n"
|
||||
@ -280,17 +280,17 @@ msgstr "בכל אופן, קישור מדור symlink נמצא; הוסר.\n"
|
||||
#: mediagoblin/gmg_commands/assetlink.py:112
|
||||
#, python-format
|
||||
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
|
||||
#, python-format
|
||||
msgid "Skipping \"%s\"; already set up.\n"
|
||||
msgstr ""
|
||||
msgstr "מדלג על \"%s\"; כבר מוגדר.\n"
|
||||
|
||||
#: mediagoblin/gmg_commands/assetlink.py:124
|
||||
#, python-format
|
||||
msgid "Old link found for \"%s\"; removing.\n"
|
||||
msgstr ""
|
||||
msgstr "קישור ישן נמצא עבור \"%s\"; מסיר כעת.\n"
|
||||
|
||||
#: mediagoblin/meddleware/csrf.py:134
|
||||
msgid ""
|
||||
@ -306,7 +306,7 @@ msgstr "צר לי, אינני תומך בטיפוס קובץ זה :("
|
||||
|
||||
#: mediagoblin/media_types/pdf/processing.py:136
|
||||
msgid "unoconv failing to run, check log file"
|
||||
msgstr ""
|
||||
msgstr "unoconv נכשל לפעול, בדוק קובץ יומן"
|
||||
|
||||
#: mediagoblin/media_types/video/processing.py:37
|
||||
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"
|
||||
" or\n"
|
||||
" <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/themes/airy/templates/mediagoblin/bits/logo.html:23
|
||||
@ -664,11 +664,11 @@ msgstr "שמור שינויים"
|
||||
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
|
||||
#, python-format
|
||||
msgid "Changing %(username)s's password"
|
||||
msgstr ""
|
||||
msgstr "משנה כעת את הסיסמה של %(username)s'"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
msgstr "שמור"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
|
||||
#, python-format
|
||||
@ -699,7 +699,7 @@ msgstr "שינוי הגדרות חשבון עבור %(username)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
|
||||
msgid "Change your password."
|
||||
msgstr ""
|
||||
msgstr "שנה את סיסמתך."
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
|
||||
msgid "Delete my account"
|
||||
@ -773,7 +773,7 @@ msgstr "תמונה עבור %(media_title)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
|
||||
msgid "PDF file"
|
||||
msgstr ""
|
||||
msgstr "קובץ PDF"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
|
||||
msgid "Toggle Rotate"
|
||||
@ -929,15 +929,15 @@ msgstr "הוסף את תגובה זו"
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
|
||||
#, python-format
|
||||
msgid "%(formatted_time)s ago"
|
||||
msgstr ""
|
||||
msgstr "מלפני %(formatted_time)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
|
||||
msgid "Added"
|
||||
msgstr ""
|
||||
msgstr "התווסף"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
|
||||
msgid "Created"
|
||||
msgstr ""
|
||||
msgstr "נוצר"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
|
||||
@ -1125,27 +1125,27 @@ msgstr "לא נראה שקיים עמוד בכתובת זו. צר לי!</p><p>א
|
||||
|
||||
#: mediagoblin/tools/timesince.py:62
|
||||
msgid "year"
|
||||
msgstr ""
|
||||
msgstr "שנה"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:63
|
||||
msgid "month"
|
||||
msgstr ""
|
||||
msgstr "חודש"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:64
|
||||
msgid "week"
|
||||
msgstr ""
|
||||
msgstr "שבוע"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:65
|
||||
msgid "day"
|
||||
msgstr ""
|
||||
msgstr "יום"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:66
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
msgstr "שעה"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:67
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
msgstr "דקה"
|
||||
|
||||
#: mediagoblin/user_pages/forms.py:23
|
||||
msgid "Comment"
|
||||
@ -1184,7 +1184,7 @@ msgstr "הגיב/ה על פרסומך"
|
||||
|
||||
#: mediagoblin/user_pages/views.py:169
|
||||
msgid "Sorry, comments are disabled."
|
||||
msgstr ""
|
||||
msgstr "מצטערים, תגובות מנוטרלות."
|
||||
|
||||
#: mediagoblin/user_pages/views.py:174
|
||||
msgid "Oops, your comment was empty."
|
||||
|
Binary file not shown.
@ -10,8 +10,8 @@ msgstr ""
|
||||
"Project-Id-Version: GNU MediaGoblin\n"
|
||||
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
|
||||
"POT-Creation-Date: 2013-05-27 13:54-0500\n"
|
||||
"PO-Revision-Date: 2013-05-27 18:54+0000\n"
|
||||
"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
|
||||
"PO-Revision-Date: 2013-06-05 22:51+0000\n"
|
||||
"Last-Translator: tryggvib <tryggvib@fsfi.is>\n"
|
||||
"Language-Team: Icelandic (Iceland) (http://www.transifex.com/projects/p/mediagoblin/language/is_IS/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -35,7 +35,7 @@ msgstr "Netfang"
|
||||
|
||||
#: mediagoblin/auth/forms.py:41
|
||||
msgid "Username or Email"
|
||||
msgstr ""
|
||||
msgstr "Notandanafn eða tölvupóstur"
|
||||
|
||||
#: mediagoblin/auth/forms.py:52
|
||||
msgid "Username or email"
|
||||
@ -262,7 +262,7 @@ msgstr "Vitlaust lykilorð"
|
||||
|
||||
#: mediagoblin/edit/views.py:363
|
||||
msgid "Your password was changed successfully"
|
||||
msgstr ""
|
||||
msgstr "Það tókst að breyta lykilorðinu þínu"
|
||||
|
||||
#: mediagoblin/gmg_commands/assetlink.py:60
|
||||
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
|
||||
#, python-format
|
||||
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
|
||||
#, python-format
|
||||
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
|
||||
#, python-format
|
||||
msgid "Old link found for \"%s\"; removing.\n"
|
||||
msgstr ""
|
||||
msgstr "Gamall tengill fannst fyrir \"%s\"; fjarlægi.\n"
|
||||
|
||||
#: mediagoblin/meddleware/csrf.py:134
|
||||
msgid ""
|
||||
@ -305,7 +305,7 @@ msgstr "Ég styð því miður ekki þessa gerð af skrám :("
|
||||
|
||||
#: mediagoblin/media_types/pdf/processing.py:136
|
||||
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
|
||||
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"
|
||||
" or\n"
|
||||
" <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/themes/airy/templates/mediagoblin/bits/logo.html:23
|
||||
@ -663,11 +663,11 @@ msgstr "Vista breytingar"
|
||||
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
|
||||
#, python-format
|
||||
msgid "Changing %(username)s's password"
|
||||
msgstr ""
|
||||
msgstr "Breyti lykilorði fyrir notandann: %(username)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
msgstr "Vista"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
|
||||
#, python-format
|
||||
@ -698,7 +698,7 @@ msgstr "Breyti notandaaðgangsstillingum fyrir: %(username)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
|
||||
msgid "Change your password."
|
||||
msgstr ""
|
||||
msgstr "Breyta lykilorðinu þínu."
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
|
||||
msgid "Delete my account"
|
||||
@ -772,7 +772,7 @@ msgstr "Mynd fyrir %(media_title)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
|
||||
msgid "PDF file"
|
||||
msgstr ""
|
||||
msgstr "PDF skrá"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
|
||||
msgid "Toggle Rotate"
|
||||
@ -928,15 +928,15 @@ msgstr "Senda inn þessa athugasemd"
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
|
||||
#, python-format
|
||||
msgid "%(formatted_time)s ago"
|
||||
msgstr ""
|
||||
msgstr "Fyrir %(formatted_time)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
|
||||
msgid "Added"
|
||||
msgstr ""
|
||||
msgstr "Bætt við"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
|
||||
msgid "Created"
|
||||
msgstr ""
|
||||
msgstr "Skapað"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
|
||||
#: 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
|
||||
msgid "year"
|
||||
msgstr ""
|
||||
msgstr "ár"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:63
|
||||
msgid "month"
|
||||
msgstr ""
|
||||
msgstr "mánuður"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:64
|
||||
msgid "week"
|
||||
msgstr ""
|
||||
msgstr "vika"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:65
|
||||
msgid "day"
|
||||
msgstr ""
|
||||
msgstr "dagur"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:66
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
msgstr "klukkustund"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:67
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
msgstr "mínúta"
|
||||
|
||||
#: mediagoblin/user_pages/forms.py:23
|
||||
msgid "Comment"
|
||||
@ -1183,7 +1183,7 @@ msgstr "skrifaði athugasemd við færsluna þína"
|
||||
|
||||
#: mediagoblin/user_pages/views.py:169
|
||||
msgid "Sorry, comments are disabled."
|
||||
msgstr ""
|
||||
msgstr "Því miður, athugasemdir eru óvirkar."
|
||||
|
||||
#: mediagoblin/user_pages/views.py:174
|
||||
msgid "Oops, your comment was empty."
|
||||
|
Binary file not shown.
@ -10,8 +10,8 @@ msgstr ""
|
||||
"Project-Id-Version: GNU MediaGoblin\n"
|
||||
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
|
||||
"POT-Creation-Date: 2013-05-27 13:54-0500\n"
|
||||
"PO-Revision-Date: 2013-05-27 18:54+0000\n"
|
||||
"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
|
||||
"PO-Revision-Date: 2013-05-31 15:40+0000\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"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -35,7 +35,7 @@ msgstr "Epost"
|
||||
|
||||
#: mediagoblin/auth/forms.py:41
|
||||
msgid "Username or Email"
|
||||
msgstr ""
|
||||
msgstr "Brukarnamn eller epost"
|
||||
|
||||
#: mediagoblin/auth/forms.py:52
|
||||
msgid "Username or email"
|
||||
@ -262,7 +262,7 @@ msgstr "Feil passord"
|
||||
|
||||
#: mediagoblin/edit/views.py:363
|
||||
msgid "Your password was changed successfully"
|
||||
msgstr ""
|
||||
msgstr "Endra passord"
|
||||
|
||||
#: mediagoblin/gmg_commands/assetlink.py:60
|
||||
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
|
||||
#, python-format
|
||||
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
|
||||
#, python-format
|
||||
msgid "Skipping \"%s\"; already set up.\n"
|
||||
msgstr ""
|
||||
msgstr "Hopper over «%s»: allereie satt opp.\n"
|
||||
|
||||
#: mediagoblin/gmg_commands/assetlink.py:124
|
||||
#, python-format
|
||||
msgid "Old link found for \"%s\"; removing.\n"
|
||||
msgstr ""
|
||||
msgstr "Gamal lenkje funnen for «%s»; fjernar.\n"
|
||||
|
||||
#: mediagoblin/meddleware/csrf.py:134
|
||||
msgid ""
|
||||
@ -305,7 +305,7 @@ msgstr "Orsak, stør ikkje den filtypen :("
|
||||
|
||||
#: mediagoblin/media_types/pdf/processing.py:136
|
||||
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
|
||||
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"
|
||||
" or\n"
|
||||
" <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/themes/airy/templates/mediagoblin/bits/logo.html:23
|
||||
@ -663,11 +663,11 @@ msgstr "Lagra"
|
||||
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
|
||||
#, python-format
|
||||
msgid "Changing %(username)s's password"
|
||||
msgstr ""
|
||||
msgstr "Endrar passordet til %(username)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
msgstr "Lagra"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
|
||||
#, python-format
|
||||
@ -698,7 +698,7 @@ msgstr "Endrar kontoinnstellingane til %(username)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
|
||||
msgid "Change your password."
|
||||
msgstr ""
|
||||
msgstr "Endra passordet ditt."
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
|
||||
msgid "Delete my account"
|
||||
@ -772,7 +772,7 @@ msgstr "Bilete for %(media_title)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
|
||||
msgid "PDF file"
|
||||
msgstr ""
|
||||
msgstr "PDF-fil"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
|
||||
msgid "Toggle Rotate"
|
||||
@ -928,15 +928,15 @@ msgstr "Legg til dette innspelet"
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
|
||||
#, python-format
|
||||
msgid "%(formatted_time)s ago"
|
||||
msgstr ""
|
||||
msgstr "%(formatted_time)s sidan"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
|
||||
msgid "Added"
|
||||
msgstr ""
|
||||
msgstr "Lagt til"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
|
||||
msgid "Created"
|
||||
msgstr ""
|
||||
msgstr "Oppretta"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
|
||||
#: 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
|
||||
msgid "year"
|
||||
msgstr ""
|
||||
msgstr "år"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:63
|
||||
msgid "month"
|
||||
msgstr ""
|
||||
msgstr "månad"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:64
|
||||
msgid "week"
|
||||
msgstr ""
|
||||
msgstr "veke"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:65
|
||||
msgid "day"
|
||||
msgstr ""
|
||||
msgstr "dag"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:66
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
msgstr "time"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:67
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
msgstr "minutt"
|
||||
|
||||
#: mediagoblin/user_pages/forms.py:23
|
||||
msgid "Comment"
|
||||
@ -1183,7 +1183,7 @@ msgstr "kom med innspel på innlegget ditt"
|
||||
|
||||
#: mediagoblin/user_pages/views.py:169
|
||||
msgid "Sorry, comments are disabled."
|
||||
msgstr ""
|
||||
msgstr "Innspel er avslege"
|
||||
|
||||
#: mediagoblin/user_pages/views.py:174
|
||||
msgid "Oops, your comment was empty."
|
||||
|
Binary file not shown.
@ -10,8 +10,8 @@ msgstr ""
|
||||
"Project-Id-Version: GNU MediaGoblin\n"
|
||||
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
|
||||
"POT-Creation-Date: 2013-05-27 13:54-0500\n"
|
||||
"PO-Revision-Date: 2013-05-27 18:54+0000\n"
|
||||
"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
|
||||
"PO-Revision-Date: 2013-05-28 13:51+0000\n"
|
||||
"Last-Translator: Sergiusz Pawlowicz <transifex@pawlowicz.name>\n"
|
||||
"Language-Team: Polish (http://www.transifex.com/projects/p/mediagoblin/language/pl/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -35,7 +35,7 @@ msgstr "Adres e-mail"
|
||||
|
||||
#: mediagoblin/auth/forms.py:41
|
||||
msgid "Username or Email"
|
||||
msgstr ""
|
||||
msgstr "Nazwa konta lub adres poczty elektronicznej"
|
||||
|
||||
#: mediagoblin/auth/forms.py:52
|
||||
msgid "Username or email"
|
||||
@ -262,7 +262,7 @@ msgstr "Nieprawidłowe hasło"
|
||||
|
||||
#: mediagoblin/edit/views.py:363
|
||||
msgid "Your password was changed successfully"
|
||||
msgstr ""
|
||||
msgstr "Twoje hasło zostało zmienione"
|
||||
|
||||
#: mediagoblin/gmg_commands/assetlink.py:60
|
||||
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
|
||||
#, python-format
|
||||
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
|
||||
#, python-format
|
||||
msgid "Skipping \"%s\"; already set up.\n"
|
||||
msgstr ""
|
||||
msgstr "Opuszczam \"%s\"; już jest gotowe.\n"
|
||||
|
||||
#: mediagoblin/gmg_commands/assetlink.py:124
|
||||
#, python-format
|
||||
msgid "Old link found for \"%s\"; removing.\n"
|
||||
msgstr ""
|
||||
msgstr "Znaleziono stary odnośnik dla \"%s\"; usuwam.\n"
|
||||
|
||||
#: mediagoblin/meddleware/csrf.py:134
|
||||
msgid ""
|
||||
@ -305,7 +305,7 @@ msgstr "NIestety, nie obsługujemy tego typu plików :-("
|
||||
|
||||
#: mediagoblin/media_types/pdf/processing.py:136
|
||||
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
|
||||
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"
|
||||
" or\n"
|
||||
" <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/themes/airy/templates/mediagoblin/bits/logo.html:23
|
||||
@ -663,11 +663,11 @@ msgstr "Zapisz zmiany"
|
||||
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
|
||||
#, python-format
|
||||
msgid "Changing %(username)s's password"
|
||||
msgstr ""
|
||||
msgstr "Zmieniam hasło użytkownika %(username)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
msgstr "Zachowaj"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
|
||||
#, python-format
|
||||
@ -698,7 +698,7 @@ msgstr "Zmiana ustawień konta %(username)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
|
||||
msgid "Change your password."
|
||||
msgstr ""
|
||||
msgstr "Zmień swoje hasło."
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
|
||||
msgid "Delete my account"
|
||||
@ -772,7 +772,7 @@ msgstr "Grafika dla %(media_title)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
|
||||
msgid "PDF file"
|
||||
msgstr ""
|
||||
msgstr "Plik PDF"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
|
||||
msgid "Toggle Rotate"
|
||||
@ -928,15 +928,15 @@ msgstr "Dodaj komentarz"
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
|
||||
#, python-format
|
||||
msgid "%(formatted_time)s ago"
|
||||
msgstr ""
|
||||
msgstr "%(formatted_time)s temu"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
|
||||
msgid "Added"
|
||||
msgstr ""
|
||||
msgstr "Dodano"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
|
||||
msgid "Created"
|
||||
msgstr ""
|
||||
msgstr "Utworzono"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
|
||||
#: 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
|
||||
msgid "year"
|
||||
msgstr ""
|
||||
msgstr "rok"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:63
|
||||
msgid "month"
|
||||
msgstr ""
|
||||
msgstr "miesiąc"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:64
|
||||
msgid "week"
|
||||
msgstr ""
|
||||
msgstr "tydzień"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:65
|
||||
msgid "day"
|
||||
msgstr ""
|
||||
msgstr "dzień"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:66
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
msgstr "godzina"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:67
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
msgstr "minuta"
|
||||
|
||||
#: mediagoblin/user_pages/forms.py:23
|
||||
msgid "Comment"
|
||||
@ -1183,7 +1183,7 @@ msgstr "komentarze do twojego wpisu"
|
||||
|
||||
#: mediagoblin/user_pages/views.py:169
|
||||
msgid "Sorry, comments are disabled."
|
||||
msgstr ""
|
||||
msgstr "Komentowanie jest wyłączone."
|
||||
|
||||
#: mediagoblin/user_pages/views.py:174
|
||||
msgid "Oops, your comment was empty."
|
||||
|
Binary file not shown.
@ -10,8 +10,8 @@ msgstr ""
|
||||
"Project-Id-Version: GNU MediaGoblin\n"
|
||||
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
|
||||
"POT-Creation-Date: 2013-05-27 13:54-0500\n"
|
||||
"PO-Revision-Date: 2013-05-27 18:54+0000\n"
|
||||
"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
|
||||
"PO-Revision-Date: 2013-05-27 20:40+0000\n"
|
||||
"Last-Translator: George Pop <gapop@hotmail.com>\n"
|
||||
"Language-Team: Romanian (http://www.transifex.com/projects/p/mediagoblin/language/ro/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -35,7 +35,7 @@ msgstr "Adresa de e-mail"
|
||||
|
||||
#: mediagoblin/auth/forms.py:41
|
||||
msgid "Username or Email"
|
||||
msgstr ""
|
||||
msgstr "Numele de utilizator sau adresa de e-mail"
|
||||
|
||||
#: mediagoblin/auth/forms.py:52
|
||||
msgid "Username or email"
|
||||
@ -262,7 +262,7 @@ msgstr "Parolă incorectă"
|
||||
|
||||
#: mediagoblin/edit/views.py:363
|
||||
msgid "Your password was changed successfully"
|
||||
msgstr ""
|
||||
msgstr "Parola a fost schimbată cu succes"
|
||||
|
||||
#: mediagoblin/gmg_commands/assetlink.py:60
|
||||
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
|
||||
#, python-format
|
||||
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
|
||||
#, python-format
|
||||
msgid "Skipping \"%s\"; already set up.\n"
|
||||
msgstr ""
|
||||
msgstr "S-a omis \"%s\"; configurat deja.\n"
|
||||
|
||||
#: mediagoblin/gmg_commands/assetlink.py:124
|
||||
#, python-format
|
||||
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
|
||||
msgid ""
|
||||
@ -305,7 +305,7 @@ msgstr "Scuze, nu recunosc acest tip de fișier :("
|
||||
|
||||
#: mediagoblin/media_types/pdf/processing.py:136
|
||||
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
|
||||
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"
|
||||
" or\n"
|
||||
" <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/themes/airy/templates/mediagoblin/bits/logo.html:23
|
||||
@ -663,11 +663,11 @@ msgstr "Salvează modificările"
|
||||
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
|
||||
#, python-format
|
||||
msgid "Changing %(username)s's password"
|
||||
msgstr ""
|
||||
msgstr "Se modifică parola pentru %(username)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
msgstr "Salvează"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
|
||||
#, python-format
|
||||
@ -698,7 +698,7 @@ msgstr "Se modifică setările contului pentru userul %(username)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
|
||||
msgid "Change your password."
|
||||
msgstr ""
|
||||
msgstr "Modifică parolă."
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
|
||||
msgid "Delete my account"
|
||||
@ -772,7 +772,7 @@ msgstr "Imagine pentru %(media_title)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
|
||||
msgid "PDF file"
|
||||
msgstr ""
|
||||
msgstr "Fișier PDF"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
|
||||
msgid "Toggle Rotate"
|
||||
@ -928,15 +928,15 @@ msgstr "Trimite acest comentariu"
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
|
||||
#, python-format
|
||||
msgid "%(formatted_time)s ago"
|
||||
msgstr ""
|
||||
msgstr "în urmă cu %(formatted_time)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
|
||||
msgid "Added"
|
||||
msgstr ""
|
||||
msgstr "Adăugat"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
|
||||
msgid "Created"
|
||||
msgstr ""
|
||||
msgstr "Creat"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
|
||||
#: 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
|
||||
msgid "year"
|
||||
msgstr ""
|
||||
msgstr "anul"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:63
|
||||
msgid "month"
|
||||
msgstr ""
|
||||
msgstr "luna"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:64
|
||||
msgid "week"
|
||||
msgstr ""
|
||||
msgstr "săptămâna"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:65
|
||||
msgid "day"
|
||||
msgstr ""
|
||||
msgstr "ziua"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:66
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
msgstr "ora"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:67
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
msgstr "minutul"
|
||||
|
||||
#: mediagoblin/user_pages/forms.py:23
|
||||
msgid "Comment"
|
||||
@ -1183,7 +1183,7 @@ msgstr "a făcut un comentariu la postarea ta"
|
||||
|
||||
#: mediagoblin/user_pages/views.py:169
|
||||
msgid "Sorry, comments are disabled."
|
||||
msgstr ""
|
||||
msgstr "Comentariile sunt dezactivate."
|
||||
|
||||
#: mediagoblin/user_pages/views.py:174
|
||||
msgid "Oops, your comment was empty."
|
||||
|
Binary file not shown.
@ -10,8 +10,8 @@ msgstr ""
|
||||
"Project-Id-Version: GNU MediaGoblin\n"
|
||||
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
|
||||
"POT-Creation-Date: 2013-05-27 13:54-0500\n"
|
||||
"PO-Revision-Date: 2013-05-27 18:54+0000\n"
|
||||
"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
|
||||
"PO-Revision-Date: 2013-06-01 21:08+0000\n"
|
||||
"Last-Translator: aleksejrs <deletesoftware@yandex.ru>\n"
|
||||
"Language-Team: Russian (http://www.transifex.com/projects/p/mediagoblin/language/ru/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -35,7 +35,7 @@ msgstr "Адрес электронной почты"
|
||||
|
||||
#: mediagoblin/auth/forms.py:41
|
||||
msgid "Username or Email"
|
||||
msgstr ""
|
||||
msgstr "Имя пользователя или адрес электронной почты"
|
||||
|
||||
#: mediagoblin/auth/forms.py:52
|
||||
msgid "Username or email"
|
||||
@ -262,7 +262,7 @@ msgstr "Неправильный пароль"
|
||||
|
||||
#: mediagoblin/edit/views.py:363
|
||||
msgid "Your password was changed successfully"
|
||||
msgstr ""
|
||||
msgstr "Ваш пароль сменён успешно"
|
||||
|
||||
#: mediagoblin/gmg_commands/assetlink.py:60
|
||||
msgid "Cannot link theme... no theme set\n"
|
||||
@ -663,11 +663,11 @@ msgstr "Сохранить изменения"
|
||||
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
|
||||
#, python-format
|
||||
msgid "Changing %(username)s's password"
|
||||
msgstr ""
|
||||
msgstr "Смена пароля %(username)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
msgstr "Сохранить"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
|
||||
#, python-format
|
||||
@ -698,7 +698,7 @@ msgstr "Настройка учётной записи %(username)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
|
||||
msgid "Change your password."
|
||||
msgstr ""
|
||||
msgstr "Сменить пароль"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
|
||||
msgid "Delete my account"
|
||||
@ -772,7 +772,7 @@ msgstr "Изображение «%(media_title)s»"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
|
||||
msgid "PDF file"
|
||||
msgstr ""
|
||||
msgstr "PDF-файл"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
|
||||
msgid "Toggle Rotate"
|
||||
@ -928,15 +928,15 @@ msgstr "Добавить этот комментарий"
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
|
||||
#, python-format
|
||||
msgid "%(formatted_time)s ago"
|
||||
msgstr ""
|
||||
msgstr "%(formatted_time)s назад"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
|
||||
msgid "Added"
|
||||
msgstr ""
|
||||
msgstr "Добавлен"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
|
||||
msgid "Created"
|
||||
msgstr ""
|
||||
msgstr "Создан"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
|
||||
@ -1144,7 +1144,7 @@ msgstr ""
|
||||
|
||||
#: mediagoblin/tools/timesince.py:67
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
msgstr "мин"
|
||||
|
||||
#: mediagoblin/user_pages/forms.py:23
|
||||
msgid "Comment"
|
||||
@ -1183,7 +1183,7 @@ msgstr "оставил комментарий к вашему файлу"
|
||||
|
||||
#: mediagoblin/user_pages/views.py:169
|
||||
msgid "Sorry, comments are disabled."
|
||||
msgstr ""
|
||||
msgstr "Сожалеем: возможность комментирования отключена."
|
||||
|
||||
#: mediagoblin/user_pages/views.py:174
|
||||
msgid "Oops, your comment was empty."
|
||||
|
Binary file not shown.
@ -14,8 +14,8 @@ msgstr ""
|
||||
"Project-Id-Version: GNU MediaGoblin\n"
|
||||
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
|
||||
"POT-Creation-Date: 2013-05-27 13:54-0500\n"
|
||||
"PO-Revision-Date: 2013-05-27 18:54+0000\n"
|
||||
"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
|
||||
"PO-Revision-Date: 2013-05-28 07:47+0000\n"
|
||||
"Last-Translator: martin <zatroch.martin@gmail.com>\n"
|
||||
"Language-Team: Slovak (http://www.transifex.com/projects/p/mediagoblin/language/sk/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -39,7 +39,7 @@ msgstr "Email adresse"
|
||||
|
||||
#: mediagoblin/auth/forms.py:41
|
||||
msgid "Username or Email"
|
||||
msgstr ""
|
||||
msgstr "Použivateľské meno alebo e-mail"
|
||||
|
||||
#: mediagoblin/auth/forms.py:52
|
||||
msgid "Username or email"
|
||||
@ -266,7 +266,7 @@ msgstr "Nesprávne heslo"
|
||||
|
||||
#: mediagoblin/edit/views.py:363
|
||||
msgid "Your password was changed successfully"
|
||||
msgstr ""
|
||||
msgstr "Tvoje heslo bolo úspešne zmenené"
|
||||
|
||||
#: mediagoblin/gmg_commands/assetlink.py:60
|
||||
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
|
||||
#, python-format
|
||||
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
|
||||
#, python-format
|
||||
msgid "Skipping \"%s\"; already set up.\n"
|
||||
msgstr ""
|
||||
msgstr "Preskakujem \"%s\"; opakovane nastavené.\n"
|
||||
|
||||
#: mediagoblin/gmg_commands/assetlink.py:124
|
||||
#, python-format
|
||||
msgid "Old link found for \"%s\"; removing.\n"
|
||||
msgstr ""
|
||||
msgstr "Nájdený starý odkaz pre \"%s\"; odstraňujem.\n"
|
||||
|
||||
#: mediagoblin/meddleware/csrf.py:134
|
||||
msgid ""
|
||||
@ -309,7 +309,7 @@ msgstr "Prepáč, nepodporujem tento typ súborov =("
|
||||
|
||||
#: mediagoblin/media_types/pdf/processing.py:136
|
||||
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
|
||||
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"
|
||||
" or\n"
|
||||
" <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/themes/airy/templates/mediagoblin/bits/logo.html:23
|
||||
@ -667,11 +667,11 @@ msgstr "Uložiť zmeny"
|
||||
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
|
||||
#, python-format
|
||||
msgid "Changing %(username)s's password"
|
||||
msgstr ""
|
||||
msgstr "Mením heslo používateľa %(username)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
msgstr "Uložiť"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
|
||||
#, python-format
|
||||
@ -702,7 +702,7 @@ msgstr "Mením nastavenia účtu používateľa %(username)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
|
||||
msgid "Change your password."
|
||||
msgstr ""
|
||||
msgstr "Zmeniť svoje heslo."
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
|
||||
msgid "Delete my account"
|
||||
@ -776,7 +776,7 @@ msgstr "Obrázok pre %(media_title)s"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
|
||||
msgid "PDF file"
|
||||
msgstr ""
|
||||
msgstr "PDF súbor"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
|
||||
msgid "Toggle Rotate"
|
||||
@ -932,15 +932,15 @@ msgstr "Pridať tento komentár"
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
|
||||
#, python-format
|
||||
msgid "%(formatted_time)s ago"
|
||||
msgstr ""
|
||||
msgstr "pred %(formatted_time)s "
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
|
||||
msgid "Added"
|
||||
msgstr ""
|
||||
msgstr "Pridané"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
|
||||
msgid "Created"
|
||||
msgstr ""
|
||||
msgstr "Vytvorené"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
|
||||
#: 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
|
||||
msgid "year"
|
||||
msgstr ""
|
||||
msgstr "rok"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:63
|
||||
msgid "month"
|
||||
msgstr ""
|
||||
msgstr "mesiac"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:64
|
||||
msgid "week"
|
||||
msgstr ""
|
||||
msgstr "týždeň"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:65
|
||||
msgid "day"
|
||||
msgstr ""
|
||||
msgstr "deň"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:66
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
msgstr "hodina"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:67
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
msgstr "minúta"
|
||||
|
||||
#: mediagoblin/user_pages/forms.py:23
|
||||
msgid "Comment"
|
||||
@ -1187,7 +1187,7 @@ msgstr "okmentoval tvoj príspevok"
|
||||
|
||||
#: mediagoblin/user_pages/views.py:169
|
||||
msgid "Sorry, comments are disabled."
|
||||
msgstr ""
|
||||
msgstr "Prepáč, komentovanie je vypnuté."
|
||||
|
||||
#: mediagoblin/user_pages/views.py:174
|
||||
msgid "Oops, your comment was empty."
|
||||
|
BIN
mediagoblin/i18n/tr_TR/LC_MESSAGES/mediagoblin.mo
Normal file
BIN
mediagoblin/i18n/tr_TR/LC_MESSAGES/mediagoblin.mo
Normal file
Binary file not shown.
1252
mediagoblin/i18n/tr_TR/LC_MESSAGES/mediagoblin.po
Normal file
1252
mediagoblin/i18n/tr_TR/LC_MESSAGES/mediagoblin.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
mediagoblin/i18n/zh_CN/LC_MESSAGES/mediagoblin.mo
Normal file
BIN
mediagoblin/i18n/zh_CN/LC_MESSAGES/mediagoblin.mo
Normal file
Binary file not shown.
1256
mediagoblin/i18n/zh_CN/LC_MESSAGES/mediagoblin.po
Normal file
1256
mediagoblin/i18n/zh_CN/LC_MESSAGES/mediagoblin.po
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -5,14 +5,16 @@
|
||||
# Translators:
|
||||
# <chc@citi.sinica.edu.tw>, 2011
|
||||
# Harry Chen <harryhow@gmail.com>, 2011-2012
|
||||
# medicalwei <medicalwei@gmail.com>, 2013
|
||||
# medicalwei <medicalwei@gmail.com>, 2012
|
||||
# m13253 <m13253@hotmail.com>, 2013
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: GNU MediaGoblin\n"
|
||||
"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
|
||||
"POT-Creation-Date: 2013-05-27 13:54-0500\n"
|
||||
"PO-Revision-Date: 2013-05-27 18:54+0000\n"
|
||||
"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
|
||||
"PO-Revision-Date: 2013-06-16 01:40+0000\n"
|
||||
"Last-Translator: m13253 <m13253@hotmail.com>\n"
|
||||
"Language-Team: Chinese (Taiwan) (http://www.transifex.com/projects/p/mediagoblin/language/zh_TW/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -36,7 +38,7 @@ msgstr "Email 位址"
|
||||
|
||||
#: mediagoblin/auth/forms.py:41
|
||||
msgid "Username or Email"
|
||||
msgstr ""
|
||||
msgstr "使用者名稱或 email"
|
||||
|
||||
#: mediagoblin/auth/forms.py:52
|
||||
msgid "Username or email"
|
||||
@ -44,15 +46,15 @@ msgstr "使用者名稱或 email"
|
||||
|
||||
#: mediagoblin/auth/tools.py:31
|
||||
msgid "Invalid User name or email address."
|
||||
msgstr ""
|
||||
msgstr "無效的使用者名稱或 email 位置。"
|
||||
|
||||
#: mediagoblin/auth/tools.py:32
|
||||
msgid "This field does not take email addresses."
|
||||
msgstr ""
|
||||
msgstr "本欄位不接受 email 位置。"
|
||||
|
||||
#: mediagoblin/auth/tools.py:33
|
||||
msgid "This field requires an email address."
|
||||
msgstr ""
|
||||
msgstr "本欄位需要 email 位置。"
|
||||
|
||||
#: mediagoblin/auth/views.py:54
|
||||
msgid "Sorry, registration is disabled on this instance."
|
||||
@ -92,11 +94,11 @@ msgstr "重送認證信。"
|
||||
msgid ""
|
||||
"If that email address (case sensitive!) is registered an email has been sent"
|
||||
" with instructions on how to change your password."
|
||||
msgstr ""
|
||||
msgstr "如果那 email 位置 (請注意大小寫) 已經註冊,寫有修改密碼步驟的 email 已經送出。"
|
||||
|
||||
#: mediagoblin/auth/views.py:269
|
||||
msgid "Couldn't find someone with that username."
|
||||
msgstr ""
|
||||
msgstr "找不到相關的使用者名稱。"
|
||||
|
||||
#: mediagoblin/auth/views.py:272
|
||||
msgid ""
|
||||
@ -173,15 +175,15 @@ msgstr "本網址出錯了"
|
||||
|
||||
#: mediagoblin/edit/forms.py:63
|
||||
msgid "License preference"
|
||||
msgstr ""
|
||||
msgstr "授權偏好"
|
||||
|
||||
#: mediagoblin/edit/forms.py:69
|
||||
msgid "This will be your default license on upload forms."
|
||||
msgstr ""
|
||||
msgstr "在上傳頁面,這將會是您預設的授權模式。"
|
||||
|
||||
#: mediagoblin/edit/forms.py:71
|
||||
msgid "Email me when others comment on my media"
|
||||
msgstr "當有人對我的媒體評論時寄信給我"
|
||||
msgstr "當有人對我的媒體留言時寄信給我"
|
||||
|
||||
#: mediagoblin/edit/forms.py:83
|
||||
msgid "The title can't be empty"
|
||||
@ -225,7 +227,7 @@ msgstr "您加上了附件「%s」!"
|
||||
|
||||
#: mediagoblin/edit/views.py:182
|
||||
msgid "You can only edit your own profile."
|
||||
msgstr ""
|
||||
msgstr "您只能修改您自己的個人檔案。"
|
||||
|
||||
#: mediagoblin/edit/views.py:188
|
||||
msgid "You are editing a user's profile. Proceed with caution."
|
||||
@ -241,7 +243,7 @@ msgstr "帳號設定已儲存"
|
||||
|
||||
#: mediagoblin/edit/views.py:274
|
||||
msgid "You need to confirm the deletion of your account."
|
||||
msgstr ""
|
||||
msgstr "您必須要確認是否刪除您的帳號。"
|
||||
|
||||
#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
|
||||
#: mediagoblin/user_pages/views.py:222
|
||||
@ -263,7 +265,7 @@ msgstr "密碼錯誤"
|
||||
|
||||
#: mediagoblin/edit/views.py:363
|
||||
msgid "Your password was changed successfully"
|
||||
msgstr ""
|
||||
msgstr "您的密碼已經成功修改"
|
||||
|
||||
#: mediagoblin/gmg_commands/assetlink.py:60
|
||||
msgid "Cannot link theme... no theme set\n"
|
||||
@ -280,24 +282,24 @@ msgstr "但是舊的目錄連結已經找到並移除。\n"
|
||||
#: mediagoblin/gmg_commands/assetlink.py:112
|
||||
#, python-format
|
||||
msgid "Could not link \"%s\": %s exists and is not a symlink\n"
|
||||
msgstr ""
|
||||
msgstr "無法連結「%s」:%s 存在,且不是符號連結\n"
|
||||
|
||||
#: mediagoblin/gmg_commands/assetlink.py:119
|
||||
#, python-format
|
||||
msgid "Skipping \"%s\"; already set up.\n"
|
||||
msgstr ""
|
||||
msgstr "跳過「%s」,已經建置完成。\n"
|
||||
|
||||
#: mediagoblin/gmg_commands/assetlink.py:124
|
||||
#, python-format
|
||||
msgid "Old link found for \"%s\"; removing.\n"
|
||||
msgstr ""
|
||||
msgstr "找到「%s」舊的連結,刪除中。\n"
|
||||
|
||||
#: mediagoblin/meddleware/csrf.py:134
|
||||
msgid ""
|
||||
"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 "
|
||||
"domain."
|
||||
msgstr ""
|
||||
msgstr "跨網站存取 (CSRF) 的 cookie 不存在,有可能是 cookie 阻擋程式之類的程式導致的。<br/>請允許此網域的 cookie 設定。"
|
||||
|
||||
#: mediagoblin/media_types/__init__.py:111
|
||||
#: mediagoblin/media_types/__init__.py:155
|
||||
@ -306,7 +308,7 @@ msgstr "抱歉,我不支援這樣的檔案格式 :("
|
||||
|
||||
#: mediagoblin/media_types/pdf/processing.py:136
|
||||
msgid "unoconv failing to run, check log file"
|
||||
msgstr ""
|
||||
msgstr "unoconv 無法執行,請檢查紀錄檔"
|
||||
|
||||
#: mediagoblin/media_types/video/processing.py:37
|
||||
msgid "Video transcoding failed"
|
||||
@ -335,7 +337,7 @@ msgstr "名稱"
|
||||
|
||||
#: mediagoblin/plugins/oauth/forms.py:35
|
||||
msgid "The name of the OAuth client"
|
||||
msgstr "OAuth client 的名稱"
|
||||
msgstr "OAuth 用戶程式的名稱"
|
||||
|
||||
#: mediagoblin/plugins/oauth/forms.py:36
|
||||
msgid "Description"
|
||||
@ -359,7 +361,7 @@ msgid ""
|
||||
" <strong>Public</strong> - The client can't make confidential\n"
|
||||
" requests to the GNU MediaGoblin instance (e.g. client-side\n"
|
||||
" 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
|
||||
msgid "Redirect URI"
|
||||
@ -369,23 +371,23 @@ msgstr "重定向 URI"
|
||||
msgid ""
|
||||
"The redirect URI for the applications, this field\n"
|
||||
" is <strong>required</strong> for public clients."
|
||||
msgstr "此應用程式的重定向 URI,本欄位在公開類型的 OAuth client 為必填。"
|
||||
msgstr "此應用程式的重定向 URI,本欄位在公開類型的 OAuth 用戶程式為必填。"
|
||||
|
||||
#: mediagoblin/plugins/oauth/forms.py:66
|
||||
msgid "This field is required for public clients"
|
||||
msgstr "本欄位在公開類型的 OAuth client 為必填"
|
||||
msgstr "本欄位在公開類型的用戶程式為必填"
|
||||
|
||||
#: mediagoblin/plugins/oauth/views.py:56
|
||||
msgid "The client {0} has been registered!"
|
||||
msgstr "OAuth client {0} 註冊完成!"
|
||||
msgstr "OAuth 用戶程式 {0} 註冊完成!"
|
||||
|
||||
#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
|
||||
msgid "OAuth client connections"
|
||||
msgstr ""
|
||||
msgstr "OAuth 用戶程式連線"
|
||||
|
||||
#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
|
||||
msgid "Your OAuth clients"
|
||||
msgstr ""
|
||||
msgstr "您的 OAuth 用戶程式"
|
||||
|
||||
#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
|
||||
#: mediagoblin/templates/mediagoblin/submit/collection.html:30
|
||||
@ -450,7 +452,7 @@ msgstr "媒體處理面板"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/base.html:96
|
||||
msgid "Log out"
|
||||
msgstr ""
|
||||
msgstr "登出"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/base.html:99
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
|
||||
@ -577,7 +579,7 @@ msgstr "%(username)s 您好:\n\n要啟動 GNU MediaGoblin 帳號,請在您
|
||||
msgid ""
|
||||
"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
|
||||
"%(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
|
||||
#, python-format
|
||||
@ -617,7 +619,7 @@ msgid ""
|
||||
"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
|
||||
" or\n"
|
||||
" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
|
||||
msgstr ""
|
||||
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/themes/airy/templates/mediagoblin/bits/logo.html:23
|
||||
@ -664,20 +666,20 @@ msgstr "儲存變更"
|
||||
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
|
||||
#, python-format
|
||||
msgid "Changing %(username)s's password"
|
||||
msgstr ""
|
||||
msgstr "更改 %(username)s 的密碼"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
msgstr "儲存"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
|
||||
#, python-format
|
||||
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
|
||||
msgid "Yes, really delete my account"
|
||||
msgstr ""
|
||||
msgstr "是的,我真的要把我的帳號刪除"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
|
||||
#: 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
|
||||
msgid "Change your password."
|
||||
msgstr ""
|
||||
msgstr "更改您的密碼。"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
|
||||
msgid "Delete my account"
|
||||
msgstr ""
|
||||
msgstr "刪除我的帳號"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
|
||||
#, python-format
|
||||
@ -722,7 +724,7 @@ msgstr "編輯 %(username)s 的個人檔案"
|
||||
#: mediagoblin/templates/mediagoblin/listings/tag.html:35
|
||||
#, python-format
|
||||
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/audio.html:56
|
||||
@ -773,7 +775,7 @@ msgstr " %(media_title)s 的照片"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
|
||||
msgid "PDF file"
|
||||
msgstr ""
|
||||
msgstr "PDF 檔"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
|
||||
msgid "Toggle Rotate"
|
||||
@ -781,7 +783,7 @@ msgstr "切換旋轉"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
|
||||
msgid "Perspective"
|
||||
msgstr "視角"
|
||||
msgstr "透視"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
|
||||
@ -820,14 +822,14 @@ msgid ""
|
||||
"Sorry, this video will not work because\n"
|
||||
" your web browser does not support HTML5 \n"
|
||||
" video."
|
||||
msgstr ""
|
||||
msgstr "抱歉,由於您的瀏覽器不支援 HTML5 影片,本影片無法播放"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
|
||||
msgid ""
|
||||
"You can get a modern web browser that \n"
|
||||
" can play this video at <a href=\"http://getfirefox.com\">\n"
|
||||
" http://getfirefox.com</a>!"
|
||||
msgstr ""
|
||||
msgstr "您可以在 <a href=\"http://getfirefox.com\">http://getfirefox.com</a> 取得可以播放此影片的先進瀏覽器。"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
|
||||
msgid "WebM file (640p; VP8/Vorbis)"
|
||||
@ -880,19 +882,19 @@ msgstr "移除"
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
|
||||
#, python-format
|
||||
msgid "%(username)s's collections"
|
||||
msgstr ""
|
||||
msgstr "%(username)s 的蒐藏"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
|
||||
#, python-format
|
||||
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
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hi %(username)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
|
||||
#, python-format
|
||||
@ -904,7 +906,7 @@ msgstr "%(username)s的媒體"
|
||||
msgid ""
|
||||
"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <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
|
||||
#, python-format
|
||||
@ -918,32 +920,32 @@ msgstr "❖ 瀏覽 <a href=\"%(user_url)s\">%(username)s</a> 的媒體"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
|
||||
msgid "Add a comment"
|
||||
msgstr "新增評論"
|
||||
msgstr "新增留言"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
|
||||
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 ""
|
||||
msgstr "%(formatted_time)s 前"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
|
||||
msgid "Added"
|
||||
msgstr ""
|
||||
msgstr "新增於"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
|
||||
msgid "Created"
|
||||
msgstr ""
|
||||
msgstr "建立於"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
|
||||
#, python-format
|
||||
msgid "Add “%(media_title)s” to a collection"
|
||||
msgstr ""
|
||||
msgstr "加入 “%(media_title)s” 至蒐藏"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
|
||||
msgid "+"
|
||||
@ -1022,7 +1024,7 @@ msgstr "這個使用者(還)沒有填寫個人檔案。"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
|
||||
msgid "Browse collections"
|
||||
msgstr ""
|
||||
msgstr "瀏覽蒐藏"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
|
||||
#, python-format
|
||||
@ -1047,11 +1049,11 @@ msgstr " (移除)"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/utils/collections.html:21
|
||||
msgid "Collected in"
|
||||
msgstr ""
|
||||
msgstr "蒐集了"
|
||||
|
||||
#: mediagoblin/templates/mediagoblin/utils/collections.html:40
|
||||
msgid "Add to a collection"
|
||||
msgstr ""
|
||||
msgstr "加入至蒐藏"
|
||||
|
||||
#: mediagoblin/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
|
||||
msgid "year"
|
||||
msgstr ""
|
||||
msgstr "年"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:63
|
||||
msgid "month"
|
||||
msgstr ""
|
||||
msgstr "月"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:64
|
||||
msgid "week"
|
||||
msgstr ""
|
||||
msgstr "週"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:65
|
||||
msgid "day"
|
||||
msgstr ""
|
||||
msgstr "日"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:66
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
msgstr "小時"
|
||||
|
||||
#: mediagoblin/tools/timesince.py:67
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
msgstr "分"
|
||||
|
||||
#: mediagoblin/user_pages/forms.py:23
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
msgstr "留言"
|
||||
|
||||
#: mediagoblin/user_pages/forms.py:25
|
||||
msgid ""
|
||||
@ -1168,7 +1170,7 @@ msgstr "我確定我要從蒐藏中移除此項目"
|
||||
|
||||
#: mediagoblin/user_pages/forms.py:39
|
||||
msgid "Collection"
|
||||
msgstr ""
|
||||
msgstr "蒐藏"
|
||||
|
||||
#: mediagoblin/user_pages/forms.py:40
|
||||
msgid "-- Select --"
|
||||
@ -1180,11 +1182,11 @@ msgstr "加註"
|
||||
|
||||
#: mediagoblin/user_pages/lib.py:58
|
||||
msgid "commented on your post"
|
||||
msgstr "在您的內容張貼評論"
|
||||
msgstr "在您的內容張貼留言"
|
||||
|
||||
#: mediagoblin/user_pages/views.py:169
|
||||
msgid "Sorry, comments are disabled."
|
||||
msgstr ""
|
||||
msgstr "抱歉,留言被關閉。"
|
||||
|
||||
#: mediagoblin/user_pages/views.py:174
|
||||
msgid "Oops, your comment was empty."
|
||||
|
@ -58,16 +58,20 @@ def setup_global_and_app_config(config_path):
|
||||
return global_config, app_config
|
||||
|
||||
|
||||
def setup_database():
|
||||
def setup_database(run_migrations=False):
|
||||
app_config = mg_globals.app_config
|
||||
global_config = mg_globals.global_config
|
||||
|
||||
# Load all models for media types (plugins, ...)
|
||||
load_models(app_config)
|
||||
|
||||
# Set up the database
|
||||
db = setup_connection_and_db_from_config(app_config)
|
||||
|
||||
check_db_migrations_current(db)
|
||||
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)
|
||||
|
||||
setup_globals(database=db)
|
||||
|
||||
|
@ -16,12 +16,18 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
|
||||
from celery import Celery
|
||||
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'
|
||||
|
||||
@ -97,3 +103,13 @@ def setup_celery_from_config(app_config, global_config,
|
||||
|
||||
if set_environ:
|
||||
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']))
|
||||
|
@ -14,6 +14,7 @@
|
||||
# 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 import mg_globals
|
||||
from mediagoblin.db.models import MediaEntry
|
||||
from mediagoblin.db.util import media_entries_for_tag_slug
|
||||
from mediagoblin.tools.pagination import Pagination
|
||||
@ -80,6 +81,17 @@ def atom_feed(request):
|
||||
link = request.urlgen('index', qualified=True)
|
||||
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.limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS)
|
||||
|
||||
@ -87,9 +99,8 @@ def atom_feed(request):
|
||||
feed_title,
|
||||
feed_url=request.url,
|
||||
id=link,
|
||||
links=[{'href': link,
|
||||
'rel': 'alternate',
|
||||
'type': 'text/html'}])
|
||||
links=atomlinks)
|
||||
|
||||
for entry in cursor:
|
||||
feed.add(entry.get('title'),
|
||||
entry.description_html,
|
||||
|
@ -111,7 +111,7 @@ class CsrfMeddleware(BaseMeddleware):
|
||||
httponly=True)
|
||||
|
||||
# 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):
|
||||
"""Generate a new token to use for CSRF protection."""
|
||||
|
@ -15,12 +15,10 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import tempfile
|
||||
|
||||
from mediagoblin import mg_globals
|
||||
from mediagoblin.tools.common import import_component
|
||||
from mediagoblin.tools.pluginapi import hook_handle
|
||||
from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
@ -52,36 +50,6 @@ class MediaManagerBase(object):
|
||||
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):
|
||||
'''
|
||||
Iterate through the enabled media types and find those suited
|
||||
@ -98,40 +66,18 @@ def sniff_media(media):
|
||||
media_file.write(media.stream.read())
|
||||
media.stream.seek(0)
|
||||
|
||||
for media_type, manager in get_media_managers():
|
||||
_log.info('Sniffing {0}'.format(media_type))
|
||||
if manager.sniff_handler(media_file, media=media):
|
||||
_log.info('{0} accepts the file'.format(media_type))
|
||||
return media_type, manager
|
||||
else:
|
||||
_log.debug('{0} did not accept the file'.format(media_type))
|
||||
media_type = hook_handle('sniff_handler', media_file, media=media)
|
||||
if media_type:
|
||||
_log.info('{0} accepts the file'.format(media_type))
|
||||
return media_type, hook_handle(('media_manager', media_type))
|
||||
else:
|
||||
_log.debug('{0} did not accept the file'.format(media_type))
|
||||
|
||||
raise FileTypeNotSupported(
|
||||
# TODO: Provide information on which file types are supported
|
||||
_(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):
|
||||
'''
|
||||
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
|
||||
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
|
||||
# the media manager
|
||||
if ext[1:] in manager.accepted_extensions:
|
||||
return media_type, manager
|
||||
# Omit the dot from the extension and match it against
|
||||
# the media manager
|
||||
if hook_handle('get_media_type_and_manager', ext[1:]):
|
||||
return hook_handle('get_media_type_and_manager', ext[1:])
|
||||
else:
|
||||
_log.info('File {0} has no file extension, let\'s hope the sniffers get it.'.format(
|
||||
filename))
|
||||
|
@ -17,15 +17,31 @@
|
||||
from mediagoblin.media_types import MediaManagerBase
|
||||
from mediagoblin.media_types.ascii.processing import process_ascii, \
|
||||
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):
|
||||
human_readable = "ASCII"
|
||||
processor = staticmethod(process_ascii)
|
||||
sniff_handler = staticmethod(sniff_handler)
|
||||
display_template = "mediagoblin/media_displays/ascii.html"
|
||||
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,
|
||||
}
|
||||
|
@ -28,17 +28,19 @@ from mediagoblin.media_types.ascii import asciitoimage
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
SUPPORTED_EXTENSIONS = ['txt', 'asc', 'nfo']
|
||||
MEDIA_TYPE = 'mediagoblin.media_types.ascii'
|
||||
|
||||
|
||||
def sniff_handler(media_file, **kw):
|
||||
_log.info('Sniffing {0}'.format(MEDIA_TYPE))
|
||||
if kw.get('media') is not None:
|
||||
name, ext = os.path.splitext(kw['media'].filename)
|
||||
clean_ext = ext[1:].lower()
|
||||
|
||||
if clean_ext in SUPPORTED_EXTENSIONS:
|
||||
return True
|
||||
return MEDIA_TYPE
|
||||
|
||||
return False
|
||||
return None
|
||||
|
||||
|
||||
def process_ascii(proc_state):
|
||||
|
@ -17,14 +17,29 @@
|
||||
from mediagoblin.media_types import MediaManagerBase
|
||||
from mediagoblin.media_types.audio.processing import process_audio, \
|
||||
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):
|
||||
human_readable = "Audio"
|
||||
processor = staticmethod(process_audio)
|
||||
sniff_handler = staticmethod(sniff_handler)
|
||||
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,
|
||||
}
|
||||
|
@ -27,19 +27,22 @@ from mediagoblin.media_types.audio.transcoders import (AudioTranscoder,
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
MEDIA_TYPE = 'mediagoblin.media_types.audio'
|
||||
|
||||
|
||||
def sniff_handler(media_file, **kw):
|
||||
_log.info('Sniffing {0}'.format(MEDIA_TYPE))
|
||||
try:
|
||||
transcoder = AudioTranscoder()
|
||||
data = transcoder.discover(media_file.name)
|
||||
except BadMediaFail:
|
||||
_log.debug('Audio discovery raised BadMediaFail')
|
||||
return False
|
||||
return None
|
||||
|
||||
if data.is_audio == True and data.is_video == False:
|
||||
return True
|
||||
return MEDIA_TYPE
|
||||
|
||||
return False
|
||||
return None
|
||||
|
||||
|
||||
def process_audio(proc_state):
|
||||
|
@ -13,23 +13,30 @@
|
||||
#
|
||||
# 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 datetime
|
||||
|
||||
from mediagoblin.media_types import MediaManagerBase
|
||||
from mediagoblin.media_types.image.processing import process_image, \
|
||||
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):
|
||||
human_readable = "Image"
|
||||
processor = staticmethod(process_image)
|
||||
sniff_handler = staticmethod(sniff_handler)
|
||||
display_template = "mediagoblin/media_displays/image.html"
|
||||
default_thumb = "images/media_thumbs/image.png"
|
||||
accepted_extensions = ["jpg", "jpeg", "png", "gif", "tiff"]
|
||||
|
||||
media_fetch_order = [u'medium', u'original', u'thumb']
|
||||
|
||||
|
||||
def get_original_date(self):
|
||||
"""
|
||||
Get the original date and time from the EXIF information. Returns
|
||||
@ -52,4 +59,14 @@ class ImageMediaManager(MediaManagerBase):
|
||||
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,
|
||||
}
|
||||
|
@ -35,6 +35,8 @@ PIL_FILTERS = {
|
||||
'BICUBIC': Image.BICUBIC,
|
||||
'ANTIALIAS': Image.ANTIALIAS}
|
||||
|
||||
MEDIA_TYPE = 'mediagoblin.media_types.image'
|
||||
|
||||
|
||||
def resize_image(proc_state, resized, keyname, target_name, new_size,
|
||||
exif_tags, workdir):
|
||||
@ -95,17 +97,18 @@ def resize_tool(proc_state, force, keyname, target_name,
|
||||
exif_tags, conversions_subdir)
|
||||
|
||||
|
||||
SUPPORTED_FILETYPES = ['png', 'gif', 'jpg', 'jpeg']
|
||||
SUPPORTED_FILETYPES = ['png', 'gif', 'jpg', 'jpeg', 'tiff']
|
||||
|
||||
|
||||
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!
|
||||
name, ext = os.path.splitext(kw['media'].filename)
|
||||
clean_ext = ext[1:].lower() # Strip the . from ext and make lowercase
|
||||
|
||||
if clean_ext in SUPPORTED_FILETYPES:
|
||||
_log.info('Found file extension in supported filetypes')
|
||||
return True
|
||||
return MEDIA_TYPE
|
||||
else:
|
||||
_log.debug('Media present, extension not found in {0}'.format(
|
||||
SUPPORTED_FILETYPES))
|
||||
@ -113,7 +116,7 @@ def sniff_handler(media_file, **kw):
|
||||
_log.warning('Need additional information (keyword argument \'media\')'
|
||||
' to be able to handle sniffing')
|
||||
|
||||
return False
|
||||
return None
|
||||
|
||||
|
||||
def process_image(proc_state):
|
||||
|
@ -17,15 +17,31 @@
|
||||
from mediagoblin.media_types import MediaManagerBase
|
||||
from mediagoblin.media_types.pdf.processing import process_pdf, \
|
||||
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):
|
||||
human_readable = "PDF"
|
||||
processor = staticmethod(process_pdf)
|
||||
sniff_handler = staticmethod(sniff_handler)
|
||||
display_template = "mediagoblin/media_displays/pdf.html"
|
||||
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,
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ from mediagoblin.tools.translate import fake_ugettext_passthrough as _
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
MEDIA_TYPE = 'mediagoblin.media_types.pdf'
|
||||
|
||||
# TODO - cache (memoize) util
|
||||
|
||||
# This is a list created via uniconv --show and hand removing some types that
|
||||
@ -163,16 +165,17 @@ def check_prerequisites():
|
||||
return True
|
||||
|
||||
def sniff_handler(media_file, **kw):
|
||||
_log.info('Sniffing {0}'.format(MEDIA_TYPE))
|
||||
if not check_prerequisites():
|
||||
return False
|
||||
return None
|
||||
if kw.get('media') is not None:
|
||||
name, ext = os.path.splitext(kw['media'].filename)
|
||||
clean_ext = ext[1:].lower()
|
||||
|
||||
if clean_ext in supported_extensions():
|
||||
return True
|
||||
return MEDIA_TYPE
|
||||
|
||||
return False
|
||||
return None
|
||||
|
||||
def create_pdf_thumb(original, thumb_filename, width, height):
|
||||
# Note: pdftocairo adds '.png', remove it
|
||||
@ -250,8 +253,8 @@ def process_pdf(proc_state):
|
||||
else:
|
||||
pdf_filename = queued_filename.rsplit('.', 1)[0] + '.pdf'
|
||||
unoconv = where('unoconv')
|
||||
call(executable=unoconv,
|
||||
args=[unoconv, '-v', '-f', 'pdf', queued_filename])
|
||||
Popen(executable=unoconv,
|
||||
args=[unoconv, '-v', '-f', 'pdf', queued_filename]).wait()
|
||||
if not os.path.exists(pdf_filename):
|
||||
_log.debug('unoconv failed to convert file to pdf')
|
||||
raise BadMediaFail()
|
||||
|
@ -17,15 +17,30 @@
|
||||
from mediagoblin.media_types import MediaManagerBase
|
||||
from mediagoblin.media_types.stl.processing import process_stl, \
|
||||
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):
|
||||
human_readable = "stereo lithographics"
|
||||
processor = staticmethod(process_stl)
|
||||
sniff_handler = staticmethod(sniff_handler)
|
||||
display_template = "mediagoblin/media_displays/stl.html"
|
||||
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,
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ from mediagoblin.media_types.stl import model_loader
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
SUPPORTED_FILETYPES = ['stl', 'obj']
|
||||
MEDIA_TYPE = 'mediagoblin.media_types.stl'
|
||||
|
||||
BLEND_FILE = pkg_resources.resource_filename(
|
||||
'mediagoblin.media_types.stl',
|
||||
@ -43,13 +44,14 @@ BLEND_SCRIPT = pkg_resources.resource_filename(
|
||||
|
||||
|
||||
def sniff_handler(media_file, **kw):
|
||||
_log.info('Sniffing {0}'.format(MEDIA_TYPE))
|
||||
if kw.get('media') is not None:
|
||||
name, ext = os.path.splitext(kw['media'].filename)
|
||||
clean_ext = ext[1:].lower()
|
||||
|
||||
|
||||
if clean_ext in SUPPORTED_FILETYPES:
|
||||
_log.info('Found file extension in supported filetypes')
|
||||
return True
|
||||
return MEDIA_TYPE
|
||||
else:
|
||||
_log.debug('Media present, extension not found in {0}'.format(
|
||||
SUPPORTED_FILETYPES))
|
||||
@ -57,7 +59,7 @@ def sniff_handler(media_file, **kw):
|
||||
_log.warning('Need additional information (keyword argument \'media\')'
|
||||
' to be able to handle sniffing')
|
||||
|
||||
return False
|
||||
return None
|
||||
|
||||
|
||||
def blender_render(config):
|
||||
|
27
mediagoblin/media_types/tools.py
Normal file
27
mediagoblin/media_types/tools.py
Normal 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.')
|
@ -17,20 +17,35 @@
|
||||
from mediagoblin.media_types import MediaManagerBase
|
||||
from mediagoblin.media_types.video.processing import process_video, \
|
||||
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):
|
||||
human_readable = "Video"
|
||||
processor = staticmethod(process_video)
|
||||
sniff_handler = staticmethod(sniff_handler)
|
||||
display_template = "mediagoblin/media_displays/video.html"
|
||||
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
|
||||
media_fetch_order = [u'webm_640', u'original']
|
||||
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,
|
||||
}
|
||||
|
@ -29,6 +29,8 @@ from .util import skip_transcode
|
||||
_log = logging.getLogger(__name__)
|
||||
_log.setLevel(logging.DEBUG)
|
||||
|
||||
MEDIA_TYPE = 'mediagoblin.media_types.video'
|
||||
|
||||
|
||||
class VideoTranscodingFail(BaseProcessingFail):
|
||||
'''
|
||||
@ -41,17 +43,18 @@ def sniff_handler(media_file, **kw):
|
||||
transcoder = transcoders.VideoTranscoder()
|
||||
data = transcoder.discover(media_file.name)
|
||||
|
||||
_log.info('Sniffing {0}'.format(MEDIA_TYPE))
|
||||
_log.debug('Discovered: {0}'.format(data))
|
||||
|
||||
if not data:
|
||||
_log.error('Could not discover {0}'.format(
|
||||
kw.get('media')))
|
||||
return False
|
||||
return None
|
||||
|
||||
if data['is_video'] == True:
|
||||
return True
|
||||
return MEDIA_TYPE
|
||||
|
||||
return False
|
||||
return None
|
||||
|
||||
|
||||
def process_video(proc_state):
|
||||
@ -186,7 +189,7 @@ def store_metadata(media_entry, metadata):
|
||||
[(key, tags_metadata[key])
|
||||
for key in [
|
||||
"application-name", "artist", "audio-codec", "bitrate",
|
||||
"container-format", "copyright", "encoder",
|
||||
"container-format", "copyright", "encoder",
|
||||
"encoder-version", "license", "nominal-bitrate", "title",
|
||||
"video-codec"]
|
||||
if key in tags_metadata])
|
||||
@ -203,7 +206,7 @@ def store_metadata(media_entry, metadata):
|
||||
dt.get_year(), dt.get_month(), dt.get_day(), dt.get_hour(),
|
||||
dt.get_minute(), dt.get_second(),
|
||||
dt.get_microsecond()).isoformat()
|
||||
|
||||
|
||||
metadata['tags'] = tags
|
||||
|
||||
# Only save this field if there's something to save
|
||||
|
@ -22,9 +22,15 @@ import logging
|
||||
import urllib
|
||||
import multiprocessing
|
||||
import gobject
|
||||
|
||||
old_argv = sys.argv
|
||||
sys.argv = []
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
|
||||
sys.argv = old_argv
|
||||
import struct
|
||||
try:
|
||||
from PIL import Image
|
||||
|
141
mediagoblin/notifications/__init__.py
Normal file
141
mediagoblin/notifications/__init__.py
Normal 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
|
25
mediagoblin/notifications/routing.py
Normal file
25
mediagoblin/notifications/routing.py
Normal 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')
|
46
mediagoblin/notifications/task.py
Normal file
46
mediagoblin/notifications/task.py
Normal 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]
|
55
mediagoblin/notifications/tools.py
Normal file
55
mediagoblin/notifications/tools.py
Normal 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}
|
54
mediagoblin/notifications/views.py
Normal file
54
mediagoblin/notifications/views.py
Normal 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))
|
88
mediagoblin/plugins/basic_auth/__init__.py
Normal file
88
mediagoblin/plugins/basic_auth/__init__.py
Normal 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,
|
||||
}
|
46
mediagoblin/plugins/basic_auth/forms.py
Normal file
46
mediagoblin/plugins/basic_auth/forms.py
Normal 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'))
|
@ -13,14 +13,8 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import random
|
||||
|
||||
import bcrypt
|
||||
|
||||
from mediagoblin.tools.mail import send_email
|
||||
from mediagoblin.tools.template import render_template
|
||||
from mediagoblin import mg_globals
|
||||
import random
|
||||
|
||||
|
||||
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_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)
|
123
mediagoblin/plugins/openid/__init__.py
Normal file
123
mediagoblin/plugins/openid/__init__.py
Normal 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,
|
||||
}
|
41
mediagoblin/plugins/openid/forms.py
Normal file
41
mediagoblin/plugins/openid/forms.py
Normal 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.')])
|
65
mediagoblin/plugins/openid/models.py
Normal file
65
mediagoblin/plugins/openid/models.py
Normal 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
|
||||
]
|
127
mediagoblin/plugins/openid/store.py
Normal file
127
mediagoblin/plugins/openid/store.py
Normal 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
|
@ -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 %} — {{ 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 %}
|
||||
|
@ -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 %} — {{ 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 %}
|
@ -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 %}
|
@ -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 %} — {{ 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 %}
|
||||
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
404
mediagoblin/plugins/openid/views.py
Normal file
404
mediagoblin/plugins/openid/views.py
Normal 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')
|
@ -35,6 +35,7 @@ def get_url_map():
|
||||
import mediagoblin.edit.routing
|
||||
import mediagoblin.webfinger.routing
|
||||
import mediagoblin.listings.routing
|
||||
import mediagoblin.notifications.routing
|
||||
|
||||
for route in PluginManager().get_routes():
|
||||
add_route(*route)
|
||||
|
@ -129,6 +129,7 @@ header {
|
||||
|
||||
.header_dropdown {
|
||||
margin-bottom: 20px;
|
||||
padding: 0px 10px 0px 10px;
|
||||
}
|
||||
|
||||
.header_dropdown li {
|
||||
@ -333,6 +334,10 @@ text-align: center;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.boolean {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
textarea#description, textarea#bio {
|
||||
resize: vertical;
|
||||
height: 100px;
|
||||
@ -384,6 +389,12 @@ a.comment_whenlink:hover, a.report_whenlink:hover {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.comment_active {
|
||||
box-shadow: 0px 0px 15px 15px #378566;
|
||||
background: #378566;
|
||||
color: #f7f7f7;
|
||||
}
|
||||
|
||||
textarea#comment_content {
|
||||
resize: vertical;
|
||||
width: 100%;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user