Merge remote-tracking branch 'upstream/master' into auth

Conflicts:
	mediagoblin/app.py
	mediagoblin/auth/forms.py
	mediagoblin/auth/tools.py
	mediagoblin/db/migrations.py
	mediagoblin/db/models.py
	mediagoblin/edit/views.py
	mediagoblin/plugins/basic_auth/tools.py
	mediagoblin/tests/test_edit.py
This commit is contained in:
Rodney Ewing 2013-06-25 13:37:21 -07:00
commit af4414a85f
80 changed files with 4233 additions and 5536 deletions

17
AUTHORS
View File

@ -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!

View File

@ -73,6 +73,7 @@ This guide covers writing new GNU MediaGoblin plugins.
pluginwriter/quickstart
pluginwriter/database
pluginwriter/api
pluginwriter/tests
Part 4: Developer's Zone

View File

@ -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
-------------

View File

@ -0,0 +1,64 @@
.. MediaGoblin Documentation
Written in 2013 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/>.
==============================
Writing unit tests for plugins
==============================
Here's a brief guide to writing unit tests for plugins. However, it
isn't really ideal. It also hasn't been well tested... yes, there's
some irony there :)
Some notes: we're using py.test and webtest for unit testing stuff.
Keep that in mind.
My suggestion is to mime the behavior of `mediagoblin/tests/` and put
that in your own plugin, like `myplugin/tests/`. Copy over
`conftest.py` and `pytest.ini` to your tests directory, but possibly
change the `test_app` fixture to match your own tests' config needs.
For example::
import pkg_resources
# [...]
@pytest.fixture()
def test_app(request):
return get_app(
request,
mgoblin_config=pkg_resources.resource_filename(
'myplugin.tests', 'myplugin_mediagoblin.ini'))
In any test module in your tests directory you can then do::
def test_somethingorother(test_app):
# real code goes here
pass
And you'll get a mediagoblin application wrapped in webtest passed in
to your environment.
If your plugin needs to define multiple configuration setups, you can
actually set up multiple fixtures very easily for this. You can just
set up multiple fixtures with different names that point to different
configs and pass them in as that named argument.
To run the tests, from mediagoblin's directory (make sure that your
plugin has been added to your mediagoblin checkout's virtualenv!) do::
./runtests.sh /path/to/myplugin/tests/
replacing `/path/to/myplugin/` with the actual path to your plugin.
NOTE: again, the above is untested, but it should probably work. If
you run into trouble, `contact us
<http://mediagoblin.org/pages/join.html>`_, preferably on IRC!

View File

@ -199,8 +199,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 +216,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

View File

@ -39,6 +39,13 @@ 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
**For theme authors**
If you have your own theme or you have any "user modified templates",
@ -51,7 +58,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

View File

@ -20,7 +20,7 @@ email_debug_mode = true
allow_registration = true
## Uncomment this to turn on video or enable other media types
## You may have to install dependencies, and will have to run ./bin/dbupdate
## 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

View File

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

View File

@ -38,6 +38,7 @@ from mediagoblin.init import (get_jinja_loader, get_staticdirector,
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__)
@ -195,6 +196,8 @@ class MediaGoblinApp(object):
# Log user out if authentication_disabled
no_auth_logout(request)
request.notifications = notifications
mg_request.setup_user_in_request(request)
request.controller_name = None

View File

@ -30,9 +30,6 @@ class ForgotPassForm(wtforms.Form):
class ChangePassForm(wtforms.Form):
password = wtforms.PasswordField(
'Password')
userid = wtforms.HiddenField(
'',
[wtforms.validators.Required()])
token = wtforms.HiddenField(
'',
[wtforms.validators.Required()])

View File

@ -18,6 +18,7 @@ import logging
import wtforms
from mediagoblin import mg_globals
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)
@ -60,11 +61,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.
@ -72,19 +74,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

View File

@ -15,10 +15,11 @@
# 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
from mediagoblin.tools.crypto import get_timed_signer_url
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
@ -136,16 +137,28 @@ 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
user.save()
messages.add_message(
@ -187,9 +200,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)
@ -259,11 +269,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)
@ -278,31 +283,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.gen_password_hash(
cp_form.password.data)
user.fp_verification_key = None
user.fp_token_expire = None
user.save()
messages.add_message(
@ -316,10 +334,20 @@ def verify_forgot_password(request):
'mediagoblin/auth/change_fp.html',
{'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):
@ -337,7 +365,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

View File

@ -22,9 +22,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)

View File

@ -26,7 +26,7 @@ from sqlalchemy.sql import and_
from migrate.changeset.constraint import UniqueConstraint
from mediagoblin.db.migration_tools import RegisterMigration, inspect_table
from mediagoblin.db.models import MediaEntry, Collection, User
from mediagoblin.db.models import MediaEntry, Collection, User, MediaComment
MIGRATIONS = {}
@ -288,8 +288,82 @@ def unique_collections_slug(db):
db.commit()
@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)
@ -302,3 +376,4 @@ def pw_hash_nullable(db):
constraint.create()
db.commit()

View File

@ -31,6 +31,8 @@ import uuid
import re
import datetime
from datetime import datetime
from werkzeug.utils import cached_property
from mediagoblin import mg_globals
@ -288,6 +290,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):

View File

@ -24,15 +24,17 @@ 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 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
@ -60,20 +62,17 @@ class User(Base, UserMixin):
# the RFC) and because it would be a mess to implement at this
# point.
email = Column(Unicode, nullable=False)
created = Column(DateTime, nullable=False, default=datetime.datetime.now)
pw_hash = Column(Unicode)
email_verified = Column(Boolean, default=False)
created = Column(DateTime, nullable=False, default=datetime.datetime.now)
status = Column(Unicode, default=u"needs_email_verification", nullable=False)
# Intented to be nullable=False, but migrations would not work for it
# set to nullable=True implicitly.
wants_comment_notification = Column(Boolean, default=True)
license_preference = Column(Unicode)
verification_key = Column(Unicode)
is_admin = Column(Boolean, default=False, nullable=False)
url = Column(Unicode)
bio = Column(UnicodeText) # ??
fp_verification_key = Column(Unicode)
fp_token_expire = Column(DateTime)
## TODO
# plugin data would be in a separate model
@ -392,6 +391,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,9 +487,103 @@ class ProcessingMetaData(Base):
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'
}
with_polymorphic(
Notification,
[ProcessingNotification, CommentNotification])
MODELS = [
User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, MediaFile, FileKeynames,
MediaAttachmentFile, ProcessingMetaData]
User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem,
MediaFile, FileKeynames, MediaAttachmentFile, ProcessingMetaData,
Notification, CommentNotification, ProcessingNotification,
CommentSubscription]
######################################################

View File

@ -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

View File

@ -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,10 @@ 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)])
license_preference = wtforms.SelectField(
_('License preference'),
[

View File

@ -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')

View File

@ -16,6 +16,7 @@
from datetime import datetime
from itsdangerous import BadSignature
from werkzeug.exceptions import Forbidden
from werkzeug.utils import secure_filename
@ -26,15 +27,19 @@ from mediagoblin import auth
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 +217,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 +229,45 @@ 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:
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.'))
else:
verification_key = get_timed_signer_url(
'mail_verification_token').dumps({
'user': user.id,
'email': new_email})
if form_validated and not form.errors:
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)
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,
@ -369,3 +396,48 @@ 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)

View File

@ -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"

View File

@ -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

View File

@ -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."

View File

@ -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."

View File

@ -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."

View File

@ -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."

View File

@ -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."

View File

@ -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."

View File

@ -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."

View File

@ -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."

View File

@ -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."

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -5,14 +5,16 @@
# Translators:
# <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."

View File

@ -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']))

View File

@ -46,7 +46,7 @@ def sniff_handler(media_file, **kw):
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

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,8 +13,6 @@
#
# 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
from mediagoblin.plugins.basic_auth import forms as auth_forms
from mediagoblin.plugins.basic_auth import tools as auth_tools
from mediagoblin.db.models import User
@ -45,7 +43,6 @@ def create_user(registration_form):
user.email = registration_form.email.data
user.pw_hash = gen_password_hash(
registration_form.password.data)
user.verification_key = unicode(uuid.uuid4())
user.save()
return user

View File

@ -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)

View File

@ -129,6 +129,7 @@ header {
.header_dropdown {
margin-bottom: 20px;
padding: 0px 10px 0px 10px;
}
.header_dropdown li {
@ -384,6 +385,12 @@ a.comment_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%;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,36 @@
'use strict';
/**
* 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/>.
*/
var notifications = {};
(function (n) {
n._base = '/';
n._endpoint = 'notifications/json';
n.init = function () {
$('.notification-gem').on('click', function () {
$('.header_dropdown_down:visible').click();
});
}
})(notifications)
$(document).ready(function () {
notifications.init();
});

File diff suppressed because it is too large Load Diff

View File

@ -75,7 +75,7 @@ class CloudFilesStorage(StorageInterface):
_log.debug('Container: {0}'.format(
self.container.name))
self.container_uri = self.container.public_uri()
self.container_uri = self.container.public_ssl_uri()
def _resolve_filepath(self, filepath):
return '/'.join(

View File

@ -34,6 +34,8 @@ from mediagoblin.media_types import sniff_media, \
from mediagoblin.submit.lib import check_file_field, prepare_queue_task, \
run_process_media, new_upload_entry
from mediagoblin.notifications import add_comment_subscription
@require_active_login
def submit_start(request):
@ -92,6 +94,8 @@ def submit_start(request):
run_process_media(entry, feed_url)
add_message(request, SUCCESS, _('Woohoo! Submitted!'))
add_comment_subscription(request.user, entry)
return redirect(request, "mediagoblin.user_pages.user_home",
user=request.user.username)
except Exception as e:

View File

@ -34,6 +34,8 @@
src="{{ request.staticdirect('/js/extlib/jquery.js') }}"></script>
<script type="text/javascript"
src="{{ request.staticdirect('/js/header_dropdown.js') }}"></script>
<script type="text/javascript"
src="{{ request.staticdirect('/js/notifications.js') }}"></script>
{# For clarification, the difference between the extra_head.html template
# and the head template hook is that the former should be used by
@ -57,6 +59,12 @@
<div class="header_right">
{%- if request.user %}
{% if request.user and request.user.status == 'active' %}
{% set notification_count = request.notifications.get_notification_count(request.user.id) %}
{% if notification_count %}
<a href="#notifications" class="notification-gem button_action" title="Notifications">
{{ notification_count }}</a>
{% endif %}
<div class="button_action header_dropdown_down">&#9660;</div>
<div class="button_action header_dropdown_up">&#9650;</div>
{% elif request.user and request.user.status == "needs_email_verification" %}
@ -109,6 +117,7 @@
</a>
</p>
{% endif %}
{% include 'mediagoblin/fragments/header_notifications.html' %}
</div>
{% endif %}
</header>

View File

@ -46,6 +46,7 @@
{% trans %}Change your password.{% endtrans %}
</a>
</p>
{{ wtforms_util.render_field_div(form.new_email) }}
<div class="form_field_input">
<p>{{ form.wants_comment_notification }}
{{ wtforms_util.render_label(form.wants_comment_notification) }}</p>

View File

@ -0,0 +1,29 @@
{#
# 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/>.
-#}
{% trans username=username, verification_url=verification_url|safe -%}
Hi,
We wanted to verify that you are {{ username }}. If this is the case, then
please follow the link below to verify your new email address.
{{ verification_url }}
If you are not {{ username }} or didn't request an email change, you can ignore
this email.
{%- endtrans %}

View File

@ -0,0 +1,40 @@
{% set notifications = request.notifications.get_notifications(request.user.id) %}
{% if notifications %}
<div class="header_notifications">
<h3>{% trans %}New comments{% endtrans %}</h3>
<ul>
{% for notification in notifications %}
{% set comment = notification.subject %}
{% set comment_author = comment.get_author %}
{% set media = comment.get_entry %}
<li class="comment_wrapper">
<div class="comment_author">
<img src="{{ request.staticdirect('/images/icon_comment.png') }}" />
<a href="{{ request.urlgen('mediagoblin.user_pages.user_home',
user=comment_author.username) }}"
class="comment_authorlink">
{{- comment_author.username -}}
</a>
<a href="{{ request.urlgen('mediagoblin.user_pages.media_home.view_comment',
comment=comment.id,
user=media.get_uploader.username,
media=media.slug_or_id) }}#comment"
class="comment_whenlink">
<span title='{{- comment.created.strftime("%I:%M%p %Y-%m-%d") -}}'>
{%- trans formatted_time=timesince(comment.created) -%}
{{ formatted_time }} ago
{%- endtrans -%}
</span>
</a>:
</div>
<div class="comment_content">
{% autoescape False -%}
{{ comment.content_html }}
{%- endautoescape %}
</div>
</li>
{% endfor %}
</ul>
</div>
{% endif %}

View File

@ -46,19 +46,21 @@
{%- endblock %}
{% block mediagoblin_media %}
{% if pdf_js %}
<iframe width=640px height=480px
src="{{ request.staticdirect('/extlib/pdf.js/web/viewer.html') }}?file={{ pdf_view }} ">
</iframe>
{% else %}
<a href="{{ pdf_view }}">
<img id="medium"
class="media_image"
src="{{ medium_view }}"
alt="{% trans media_title=media.title -%} Image for {{ media_title}}{% endtrans %}"/>
</a>
{% endif %}
{% if pdf_js %}
<iframe width="640px" height="480px"
src="{{ request.staticdirect('/extlib/pdf.js/web/viewer.html') }}?file={{ pdf_view }} ">
</iframe>
{% else %}
<a href="{{ pdf_view }}">
<img id="medium"
class="media_image"
src="{{ medium_view }}"
alt="
{%- trans media_title=media.title -%}
Image for {{ media_title}}
{%- endtrans %}"/>
</a>
{% endif %}
{% endblock %}
{% block mediagoblin_sidebar %}

View File

@ -81,6 +81,7 @@
user= media.get_uploader.username,
media_id=media.id) %}
<a class="button_action" href="{{ delete_url }}">{% trans %}Delete{% endtrans %}</a>
{% endif %}
{% autoescape False %}
<p>{{ media.description_html }}</p>
@ -94,6 +95,8 @@
class="button_action" id="button_addcomment" title="Add a comment">
{% trans %}Add a comment{% endtrans %}
</a>
{% include "mediagoblin/utils/comment-subscription.html" %}
{% endif %}
{% if request.user %}
<form action="{{ request.urlgen('mediagoblin.user_pages.media_post_comment',

View File

@ -0,0 +1,34 @@
{#
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#}
{%- if request.user %}
{% set subscription = request.notifications.get_comment_subscription(
request.user.id, media.id) %}
{% if not subscription or not subscription.notify %}
<a type="submit" href="{{ request.urlgen('mediagoblin.notifications.subscribe_comments',
user=media.get_uploader.username,
media=media.slug)}}"
class="button_action">Subscribe to comments
</a>
{% else %}
<a type="submit" href="{{ request.urlgen('mediagoblin.notifications.silence_comments',
user=media.get_uploader.username,
media=media.slug)}}"
class="button_action">Silence comments
</a>
{% endif %}
{%- endif %}

View File

@ -118,20 +118,15 @@ def test_register_views(test_app):
assert path == u'/auth/verify_email/'
parsed_get_params = urlparse.parse_qs(get_params)
### user should have these same parameters
assert parsed_get_params['userid'] == [
unicode(new_user.id)]
assert parsed_get_params['token'] == [
new_user.verification_key]
## Try verifying with bs verification key, shouldn't work
template.clear_test_template_context()
response = test_app.get(
"/auth/verify_email/?userid=%s&token=total_bs" % unicode(
new_user.id))
"/auth/verify_email/?token=total_bs")
response.follow()
context = template.TEMPLATE_TEST_CONTEXT[
'mediagoblin/user_pages/user.html']
# Correct redirect?
assert urlparse.urlsplit(response.location)[2] == '/'
# assert context['verification_successful'] == True
# TODO: Would be good to test messages here when we can do so...
new_user = mg_globals.database.User.find_one(
@ -195,35 +190,17 @@ def test_register_views(test_app):
path = urlparse.urlsplit(email_context['verification_url'])[2]
get_params = urlparse.urlsplit(email_context['verification_url'])[3]
assert path == u'/auth/forgot_password/verify/'
parsed_get_params = urlparse.parse_qs(get_params)
# user should have matching parameters
new_user = mg_globals.database.User.find_one({'username': u'happygirl'})
assert parsed_get_params['userid'] == [unicode(new_user.id)]
assert parsed_get_params['token'] == [new_user.fp_verification_key]
### The forgotten password token should be set to expire in ~ 10 days
# A few ticks have expired so there are only 9 full days left...
assert (new_user.fp_token_expire - datetime.datetime.now()).days == 9
assert path == u'/auth/forgot_password/verify/'
## Try using a bs password-changing verification key, shouldn't work
template.clear_test_template_context()
response = test_app.get(
"/auth/forgot_password/verify/?userid=%s&token=total_bs" % unicode(
new_user.id), status=404)
assert response.status.split()[0] == u'404' # status="404 NOT FOUND"
"/auth/forgot_password/verify/?token=total_bs")
response.follow()
## Try using an expired token to change password, shouldn't work
template.clear_test_template_context()
new_user = mg_globals.database.User.find_one({'username': u'happygirl'})
real_token_expiration = new_user.fp_token_expire
new_user.fp_token_expire = datetime.datetime.now()
new_user.save()
response = test_app.get("%s?%s" % (path, get_params), status=404)
assert response.status.split()[0] == u'404' # status="404 NOT FOUND"
new_user.fp_token_expire = real_token_expiration
new_user.save()
# Correct redirect?
assert urlparse.urlsplit(response.location)[2] == '/'
## Verify step 1 of password-change works -- can see form to change password
template.clear_test_template_context()
@ -234,7 +211,6 @@ def test_register_views(test_app):
template.clear_test_template_context()
response = test_app.post(
'/auth/forgot_password/verify/', {
'userid': parsed_get_params['userid'],
'password': 'iamveryveryhappy',
'token': parsed_get_params['token']})
response.follow()

View File

@ -48,7 +48,7 @@ def test_setup_celery_from_config():
assert isinstance(fake_celery_module.CELERYD_ETA_SCHEDULER_PRECISION, float)
assert fake_celery_module.CELERY_RESULT_PERSISTENT is True
assert fake_celery_module.CELERY_IMPORTS == [
'foo.bar.baz', 'this.is.an.import', 'mediagoblin.processing.task']
'foo.bar.baz', 'this.is.an.import', 'mediagoblin.processing.task', 'mediagoblin.notifications.task']
assert fake_celery_module.CELERY_RESULT_BACKEND == 'database'
assert fake_celery_module.CELERY_RESULT_DBURI == (
'sqlite:///' +

View File

@ -15,13 +15,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import urlparse
import pytest
from mediagoblin import mg_globals
from mediagoblin.db.models import User
from mediagoblin.tests.tools import fixture_add_user
from mediagoblin.tools import template
from mediagoblin import auth
from mediagoblin.tools import template, mail
class TestUserEdit(object):
@ -142,4 +141,68 @@ class TestUserEdit(object):
assert form.url.errors == [
u'This address contains errors']
def test_email_change(self, test_app):
self.login(test_app)
# Test email already in db
template.clear_test_template_context()
test_app.post(
'/edit/account/', {
'new_email': 'chris@example.com',
'password': 'toast'})
# Check form errors
context = template.TEMPLATE_TEST_CONTEXT[
'mediagoblin/edit/edit_account.html']
assert context['form'].new_email.errors == [
u'Sorry, a user with that email address already exists.']
# Test successful email change
template.clear_test_template_context()
res = test_app.post(
'/edit/account/', {
'new_email': 'new@example.com',
'password': 'toast'})
res.follow()
# Correct redirect?
assert urlparse.urlsplit(res.location)[2] == '/u/chris/'
# Make sure we get email verification and try verifying
assert len(mail.EMAIL_TEST_INBOX) == 1
message = mail.EMAIL_TEST_INBOX.pop()
assert message['To'] == 'new@example.com'
email_context = template.TEMPLATE_TEST_CONTEXT[
'mediagoblin/edit/verification.txt']
assert email_context['verification_url'] in \
message.get_payload(decode=True)
path = urlparse.urlsplit(email_context['verification_url'])[2]
assert path == u'/edit/verify_email/'
## Try verifying with bs verification key, shouldn't work
template.clear_test_template_context()
res = test_app.get(
"/edit/verify_email/?token=total_bs")
res.follow()
# Correct redirect?
assert urlparse.urlsplit(res.location)[2] == '/'
# Email shouldn't be saved
email_in_db = mg_globals.database.User.find_one(
{'email': 'new@example.com'})
email = User.query.filter_by(username='chris').first().email
assert email_in_db is None
assert email == 'chris@example.com'
# Verify email activation works
template.clear_test_template_context()
get_params = urlparse.urlsplit(email_context['verification_url'])[3]
res = test_app.get('%s?%s' % (path, get_params))
res.follow()
# New email saved?
email = User.query.filter_by(username='chris').first().email
assert email == 'new@example.com'
# test changing the url inproperly

View File

@ -28,8 +28,10 @@ def test_user_deletes_other_comments(test_app):
user_a = fixture_add_user(u"chris_a")
user_b = fixture_add_user(u"chris_b")
media_a = fixture_media_entry(uploader=user_a.id, save=False)
media_b = fixture_media_entry(uploader=user_b.id, save=False)
media_a = fixture_media_entry(uploader=user_a.id, save=False,
expunge=False, fake_upload=False)
media_b = fixture_media_entry(uploader=user_b.id, save=False,
expunge=False, fake_upload=False)
Session.add(media_a)
Session.add(media_b)
Session.flush()
@ -79,7 +81,7 @@ def test_user_deletes_other_comments(test_app):
def test_media_deletes_broken_attachment(test_app):
user_a = fixture_add_user(u"chris_a")
media = fixture_media_entry(uploader=user_a.id, save=False)
media = fixture_media_entry(uploader=user_a.id, save=False, expunge=False)
media.attachment_files.append(dict(
name=u"some name",
filepath=[u"does", u"not", u"exist"],

View File

@ -0,0 +1,151 @@
# 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 pytest
import urlparse
from mediagoblin.tools import template, mail
from mediagoblin.db.models import Notification, CommentNotification, \
CommentSubscription
from mediagoblin.db.base import Session
from mediagoblin.notifications import mark_comment_notification_seen
from mediagoblin.tests.tools import fixture_add_comment, \
fixture_media_entry, fixture_add_user, \
fixture_comment_subscription
class TestNotifications:
@pytest.fixture(autouse=True)
def setup(self, test_app):
self.test_app = test_app
# TODO: Possibly abstract into a decorator like:
# @as_authenticated_user('chris')
self.test_user = fixture_add_user()
self.current_user = None
self.login()
def login(self, username=u'chris', password=u'toast'):
response = self.test_app.post(
'/auth/login/', {
'username': username,
'password': password})
response.follow()
assert urlparse.urlsplit(response.location)[2] == '/'
assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
ctx = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
assert Session.merge(ctx['request'].user).username == username
self.current_user = ctx['request'].user
def logout(self):
self.test_app.get('/auth/logout/')
self.current_user = None
@pytest.mark.parametrize('wants_email', [True, False])
def test_comment_notification(self, wants_email):
'''
Test
- if a notification is created when posting a comment on
another users media entry.
- that the comment data is consistent and exists.
'''
user = fixture_add_user('otherperson', password='nosreprehto',
wants_comment_notification=wants_email)
user_id = user.id
media_entry = fixture_media_entry(uploader=user.id, state=u'processed')
media_entry_id = media_entry.id
subscription = fixture_comment_subscription(media_entry)
subscription_id = subscription.id
media_uri_id = '/u/{0}/m/{1}/'.format(user.username,
media_entry.id)
media_uri_slug = '/u/{0}/m/{1}/'.format(user.username,
media_entry.slug)
self.test_app.post(
media_uri_id + 'comment/add/',
{
'comment_content': u'Test comment #42'
}
)
notifications = Notification.query.filter_by(
user_id=user.id).all()
assert len(notifications) == 1
notification = notifications[0]
assert type(notification) == CommentNotification
assert notification.seen == False
assert notification.user_id == user.id
assert notification.subject.get_author.id == self.test_user.id
assert notification.subject.content == u'Test comment #42'
if wants_email == True:
assert mail.EMAIL_TEST_MBOX_INBOX == [
{'from': 'notice@mediagoblin.example.org',
'message': 'Content-Type: text/plain; \
charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: \
base64\nSubject: GNU MediaGoblin - chris commented on your \
post\nFrom: notice@mediagoblin.example.org\nTo: \
otherperson@example.com\n\nSGkgb3RoZXJwZXJzb24sCmNocmlzIGNvbW1lbnRlZCBvbiB5b3VyIHBvc3QgKGh0dHA6Ly9sb2Nh\nbGhvc3Q6ODAvdS9vdGhlcnBlcnNvbi9tL3NvbWUtdGl0bGUvYy8xLyNjb21tZW50KSBhdCBHTlUg\nTWVkaWFHb2JsaW4KClRlc3QgY29tbWVudCAjNDIKCkdOVSBNZWRpYUdvYmxpbg==\n',
'to': [u'otherperson@example.com']}]
else:
assert mail.EMAIL_TEST_MBOX_INBOX == []
# Save the ids temporarily because of DetachedInstanceError
notification_id = notification.id
comment_id = notification.subject.id
self.logout()
self.login('otherperson', 'nosreprehto')
self.test_app.get(media_uri_slug + '/c/{0}/'.format(comment_id))
notification = Notification.query.filter_by(id=notification_id).first()
assert notification.seen == True
self.test_app.get(media_uri_slug + '/notifications/silence/')
subscription = CommentSubscription.query.filter_by(id=subscription_id)\
.first()
assert subscription.notify == False
notifications = Notification.query.filter_by(
user_id=user_id).all()
# User should not have been notified
assert len(notifications) == 1

View File

@ -15,18 +15,17 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import os
import pkg_resources
import shutil
from functools import wraps
from paste.deploy import loadapp
from webtest import TestApp
from mediagoblin import mg_globals
from mediagoblin.db.models import User, MediaEntry, Collection
from mediagoblin.db.models import User, MediaEntry, Collection, MediaComment, \
CommentSubscription, CommentNotification
from mediagoblin.tools import testing
from mediagoblin.init.config import read_mediagoblin_config
from mediagoblin.db.base import Session
@ -171,7 +170,7 @@ def assert_db_meets_expected(db, expected):
def fixture_add_user(username=u'chris', password=u'toast',
active_user=True):
active_user=True, wants_comment_notification=True):
# Reuse existing user or create a new one
test_user = User.query.filter_by(username=username).first()
if test_user is None:
@ -184,6 +183,8 @@ def fixture_add_user(username=u'chris', password=u'toast',
test_user.email_verified = True
test_user.status = u'active'
test_user.wants_comment_notification = wants_comment_notification
test_user.save()
# Reload
@ -195,19 +196,79 @@ def fixture_add_user(username=u'chris', password=u'toast',
return test_user
def fixture_comment_subscription(entry, notify=True, send_email=None):
if send_email is None:
uploader = User.query.filter_by(id=entry.uploader).first()
send_email = uploader.wants_comment_notification
cs = CommentSubscription(
media_entry_id=entry.id,
user_id=entry.uploader,
notify=notify,
send_email=send_email)
cs.save()
cs = CommentSubscription.query.filter_by(id=cs.id).first()
Session.expunge(cs)
return cs
def fixture_add_comment_notification(entry_id, subject_id, user_id,
seen=False):
cn = CommentNotification(user_id=user_id,
seen=seen,
subject_id=subject_id)
cn.save()
cn = CommentNotification.query.filter_by(id=cn.id).first()
Session.expunge(cn)
return cn
def fixture_media_entry(title=u"Some title", slug=None,
uploader=None, save=True, gen_slug=True):
uploader=None, save=True, gen_slug=True,
state=u'unprocessed', fake_upload=True,
expunge=True):
"""
Add a media entry for testing purposes.
Caution: if you're adding multiple entries with fake_upload=True,
make sure you save between them... otherwise you'll hit an
IntegrityError from multiple newly-added-MediaEntries adding
FileKeynames at once. :)
"""
if uploader is None:
uploader = fixture_add_user().id
entry = MediaEntry()
entry.title = title
entry.slug = slug
entry.uploader = uploader or fixture_add_user().id
entry.uploader = uploader
entry.media_type = u'image'
entry.state = state
if fake_upload:
entry.media_files = {'thumb': ['a', 'b', 'c.jpg'],
'medium': ['d', 'e', 'f.png'],
'original': ['g', 'h', 'i.png']}
entry.media_type = u'mediagoblin.media_types.image'
if gen_slug:
entry.generate_slug()
if save:
entry.save()
if expunge:
entry = MediaEntry.query.filter_by(id=entry.id).first()
Session.expunge(entry)
return entry
@ -231,3 +292,25 @@ def fixture_add_collection(name=u"My first Collection", user=None):
return coll
def fixture_add_comment(author=None, media_entry=None, comment=None):
if author is None:
author = fixture_add_user().id
if media_entry is None:
media_entry = fixture_media_entry().id
if comment is None:
comment = \
'Auto-generated test comment by user #{0} on media #{0}'.format(
author, media_entry)
comment = MediaComment(author=author,
media_entry=media_entry,
content=comment)
comment.save()
Session.expunge(comment)
return comment

View File

@ -90,7 +90,12 @@ def send_email(from_addr, to_addrs, subject, message_body):
if common.TESTS_ENABLED or mg_globals.app_config['email_debug_mode']:
mhost = FakeMhost()
elif not mg_globals.app_config['email_debug_mode']:
mhost = smtplib.SMTP(
if mg_globals.app_config['email_smtp_use_ssl']:
smtp_init = smtplib.SMTP_SSL
else:
smtp_init = smtplib.SMTP
mhost = smtp_init(
mg_globals.app_config['email_smtp_host'],
mg_globals.app_config['email_smtp_port'])

View File

@ -77,7 +77,7 @@ def render_http_exception(request, exc, description):
elif stock_desc and exc.code == 404:
return render_404(request)
return render_error(request, title=exc.args[0],
return render_error(request, title='{0} {1}'.format(exc.code, exc.name),
err_msg=description,
status=exc.code)

View File

@ -25,8 +25,9 @@ from mediagoblin.tools.response import render_to_response, render_404, \
from mediagoblin.tools.translate import pass_to_ugettext as _
from mediagoblin.tools.pagination import Pagination
from mediagoblin.user_pages import forms as user_forms
from mediagoblin.user_pages.lib import (send_comment_email,
add_media_to_collection)
from mediagoblin.user_pages.lib import add_media_to_collection
from mediagoblin.notifications import trigger_notification, \
add_comment_subscription, mark_comment_notification_seen
from mediagoblin.decorators import (uses_pagination, get_user_media_entry,
get_media_entry_by_id,
@ -34,6 +35,7 @@ from mediagoblin.decorators import (uses_pagination, get_user_media_entry,
get_user_collection, get_user_collection_item, active_user_from_url)
from werkzeug.contrib.atom import AtomFeed
from werkzeug.exceptions import MethodNotAllowed
_log = logging.getLogger(__name__)
@ -110,6 +112,7 @@ def user_gallery(request, page, url_user=None):
'media_entries': media_entries,
'pagination': pagination})
MEDIA_COMMENTS_PER_PAGE = 50
@ -121,6 +124,9 @@ def media_home(request, media, page, **kwargs):
"""
comment_id = request.matchdict.get('comment', None)
if comment_id:
if request.user:
mark_comment_notification_seen(comment_id, request.user)
pagination = Pagination(
page, media.get_comments(
mg_globals.app_config['comments_ascending']),
@ -154,7 +160,8 @@ def media_post_comment(request, media):
"""
recieves POST from a MediaEntry() comment form, saves the comment.
"""
assert request.method == 'POST'
if not request.method == 'POST':
raise MethodNotAllowed()
comment = request.db.MediaComment()
comment.media_entry = media.id
@ -179,11 +186,9 @@ def media_post_comment(request, media):
request, messages.SUCCESS,
_('Your comment has been posted!'))
media_uploader = media.get_uploader
#don't send email if you comment on your own post
if (comment.author != media_uploader and
media_uploader.wants_comment_notification):
send_comment_email(media_uploader, comment, media, request)
trigger_notification(comment, media, request)
add_comment_subscription(request.user, media)
return redirect_obj(request, media)

View File

@ -14,7 +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 setuptools import setup
from setuptools import setup, find_packages
import os
import re
@ -36,7 +36,7 @@ def get_version():
setup(
name="mediagoblin",
version=get_version(),
packages=['mediagoblin'],
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
zip_safe=False,
include_package_data = True,
# scripts and dependencies
@ -57,7 +57,7 @@ setup(
'webtest<2',
'ConfigObj',
'Markdown',
'sqlalchemy>=0.7.0',
'sqlalchemy>=0.8.0',
'sqlalchemy-migrate',
'mock',
'itsdangerous',