Added basic collection functionality

This commit is contained in:
Aaron Williamson 2012-08-17 00:54:40 -04:00 committed by Joar Wandborg
parent 09e528acbb
commit be5be1154f
18 changed files with 712 additions and 15 deletions

View File

@ -143,3 +143,56 @@ class MediaCommentMixin(object):
Run through Markdown and the HTML cleaner. Run through Markdown and the HTML cleaner.
""" """
return cleaned_markdown_conversion(self.content) return cleaned_markdown_conversion(self.content)
class CollectionMixin(object):
def generate_slug(self):
# import this here due to a cyclic import issue
# (db.models -> db.mixin -> db.util -> db.models)
from mediagoblin.db.util import check_collection_slug_used
self.slug = slugify(self.title)
duplicate = check_collection_slug_used(mg_globals.database,
self.creator, self.slug, self.id)
if duplicate:
if self.id is not None:
self.slug = u"%s-%s" % (self.id, self.slug)
else:
self.slug = None
@property
def description_html(self):
"""
Rendered version of the description, run through
Markdown and cleaned with our cleaning tool.
"""
return cleaned_markdown_conversion(self.description)
@property
def slug_or_id(self):
return (self.slug or self._id)
def url_for_self(self, urlgen, **extra_args):
"""
Generate an appropriate url for ourselves
Use a slug if we have one, else use our '_id'.
"""
creator = self.get_creator
return urlgen(
'mediagoblin.user_pages.collections_home',
user=creator.username,
collection=self.slug_or_id,
**extra_args)
class CollectionItemMixin(object):
@property
def note_html(self):
"""
the actual html-rendered version of the note displayed.
Run through Markdown and the HTML cleaner.
"""
return cleaned_markdown_conversion(self.note)

View File

@ -14,7 +14,7 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from sqlalchemy import MetaData, Table, Column, Boolean, SmallInteger from sqlalchemy import MetaData, Table, Column, Boolean, SmallInteger, Integer
from mediagoblin.db.sql.util import RegisterMigration from mediagoblin.db.sql.util import RegisterMigration
@ -59,3 +59,15 @@ def add_transcoding_progress(db_conn):
col = Column('transcoding_progress', SmallInteger) col = Column('transcoding_progress', SmallInteger)
col.create(media_entry) col.create(media_entry)
db_conn.commit() db_conn.commit()
@RegisterMigration(4, MIGRATIONS)
def add_mediaentry_collections(db_conn):
metadata = MetaData(bind=db_conn.bind)
media_entry = Table('core__media_entries', metadata, autoload=True,
autoload_with=db_conn.bind)
col = Column('collections', Integer)
col.create(media_entry)
db_conn.commit()

View File

@ -33,7 +33,7 @@ from sqlalchemy.util import memoized_property
from mediagoblin.db.sql.extratypes import PathTupleWithSlashes, JSONEncoded from mediagoblin.db.sql.extratypes import PathTupleWithSlashes, JSONEncoded
from mediagoblin.db.sql.base import Base, DictReadAttrProxy from mediagoblin.db.sql.base import Base, DictReadAttrProxy
from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin, CollectionMixin, CollectionItemMixin
from mediagoblin.db.sql.base import Session from mediagoblin.db.sql.base import Session
# It's actually kind of annoying how sqlalchemy-migrate does this, if # It's actually kind of annoying how sqlalchemy-migrate does this, if
@ -103,6 +103,7 @@ class MediaEntry(Base, MediaEntryMixin):
state = Column(Unicode, default=u'unprocessed', nullable=False) state = Column(Unicode, default=u'unprocessed', nullable=False)
# or use sqlalchemy.types.Enum? # or use sqlalchemy.types.Enum?
license = Column(Unicode) license = Column(Unicode)
collected = Column(Integer, default=0)
fail_error = Column(Unicode) fail_error = Column(Unicode)
fail_metadata = Column(JSONEncoded) fail_metadata = Column(JSONEncoded)
@ -143,6 +144,11 @@ class MediaEntry(Base, MediaEntryMixin):
creator=lambda v: MediaTag(name=v["name"], slug=v["slug"]) creator=lambda v: MediaTag(name=v["name"], slug=v["slug"])
) )
collections_helper = relationship("CollectionItem",
cascade="all, delete-orphan"
)
collections = association_proxy("collections_helper", "in_collection")
## TODO ## TODO
# media_data # media_data
# fail_error # fail_error
@ -348,8 +354,58 @@ class MediaComment(Base, MediaCommentMixin):
_id = SimpleFieldAlias("id") _id = SimpleFieldAlias("id")
class Collection(Base, CollectionMixin):
__tablename__ = "core__collections"
id = Column(Integer, primary_key=True)
title = Column(Unicode, nullable=False)
slug = Column(Unicode)
created = Column(DateTime, nullable=False, default=datetime.datetime.now,
index=True)
description = Column(UnicodeText)
creator = Column(Integer, ForeignKey(User.id), nullable=False)
items = Column(Integer, default=0)
get_creator = relationship(User)
def get_collection_items(self, ascending=False):
order_col = CollectionItem.position
if not ascending:
order_col = desc(order_col)
return CollectionItem.query.filter_by(
collection=self.id).order_by(order_col)
_id = SimpleFieldAlias("id")
class CollectionItem(Base, CollectionItemMixin):
__tablename__ = "core__collection_items"
id = Column(Integer, primary_key=True)
media_entry = Column(
Integer, ForeignKey(MediaEntry.id), nullable=False, index=True)
collection = Column(Integer, ForeignKey(Collection.id), nullable=False)
note = Column(UnicodeText, nullable=True)
added = Column(DateTime, nullable=False, default=datetime.datetime.now)
position = Column(Integer)
in_collection = relationship("Collection")
get_media_entry = relationship(MediaEntry)
_id = SimpleFieldAlias("id")
__table_args__ = (
UniqueConstraint('collection', 'media_entry'),
{})
@property
def dict_view(self):
"""A dict like view on this object"""
return DictReadAttrProxy(self)
MODELS = [ MODELS = [
User, MediaEntry, Tag, MediaTag, MediaComment, MediaFile, FileKeynames, User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, MediaFile, FileKeynames,
MediaAttachmentFile] MediaAttachmentFile]

View File

@ -17,7 +17,7 @@
import sys import sys
from mediagoblin.db.sql.base import Session from mediagoblin.db.sql.base import Session
from mediagoblin.db.sql.models import MediaEntry, Tag, MediaTag from mediagoblin.db.sql.models import MediaEntry, Tag, MediaTag, Collection
from mediagoblin.tools.common import simple_printer from mediagoblin.tools.common import simple_printer
@ -310,6 +310,15 @@ def clean_orphan_tags():
Session.commit() Session.commit()
def check_collection_slug_used(dummy_db, creator_id, slug, ignore_c_id):
filt = (Collection.creator == creator_id) \
& (Collection.slug == slug)
if ignore_c_id is not None:
filt = filt & (Collection.id != ignore_c_id)
does_exist = Session.query(Collection.id).filter(filt).first() is not None
return does_exist
if __name__ == '__main__': if __name__ == '__main__':
from mediagoblin.db.sql.open import setup_connection_and_db_from_config from mediagoblin.db.sql.open import setup_connection_and_db_from_config

View File

@ -22,7 +22,7 @@ except ImportError:
if use_sql: if use_sql:
from mediagoblin.db.sql.fake import ObjectId, InvalidId, DESCENDING from mediagoblin.db.sql.fake import ObjectId, InvalidId, DESCENDING
from mediagoblin.db.sql.util import atomic_update, check_media_slug_used, \ from mediagoblin.db.sql.util import atomic_update, check_media_slug_used, \
media_entries_for_tag_slug media_entries_for_tag_slug, check_collection_slug_used
else: else:
from mediagoblin.db.mongo.util import \ from mediagoblin.db.mongo.util import \
ObjectId, InvalidId, DESCENDING, atomic_update, \ ObjectId, InvalidId, DESCENDING, atomic_update, \

View File

@ -70,6 +70,23 @@ def user_may_delete_media(controller):
return wrapper return wrapper
def user_may_alter_collection(controller):
"""
Require user ownership of the Collection to modify.
"""
@wraps(controller)
def wrapper(request, *args, **kwargs):
creator_id = request.db.User.find_one(
{'username': request.matchdict['user']}).id
if not (request.user.is_admin or
request.user._id == creator_id):
return exc.HTTPForbidden()
return controller(request, *args, **kwargs)
return wrapper
def uses_pagination(controller): def uses_pagination(controller):
""" """
Check request GET 'page' key for wrong values Check request GET 'page' key for wrong values
@ -123,6 +140,59 @@ def get_user_media_entry(controller):
return wrapper return wrapper
def get_user_collection(controller):
"""
Pass in a Collection based off of a url component
"""
@wraps(controller)
def wrapper(request, *args, **kwargs):
user = request.db.User.find_one(
{'username': request.matchdict['user']})
if not user:
return render_404(request)
collection = request.db.Collection.find_one(
{'slug': request.matchdict['collection'],
'creator': user._id})
# Still no collection? Okay, 404.
if not collection:
return render_404(request)
return controller(request, collection=collection, *args, **kwargs)
return wrapper
def get_user_collection_item(controller):
"""
Pass in a CollectionItem based off of a url component
"""
@wraps(controller)
def wrapper(request, *args, **kwargs):
user = request.db.User.find_one(
{'username': request.matchdict['user']})
if not user:
return render_404(request)
collection = request.db.Collection.find_one(
{'slug': request.matchdict['collection'],
'creator': user._id})
collection_item = request.db.CollectionItem.find_one(
{'_id': request.matchdict['collection_item'] })
# Still no collection item? Okay, 404.
if not collection_item:
return render_404(request)
return controller(request, collection_item=collection_item, *args, **kwargs)
return wrapper
def get_media_entry_by_id(controller): def get_media_entry_by_id(controller):
""" """
Pass in a MediaEntry based off of a url component Pass in a MediaEntry based off of a url component

View File

@ -77,3 +77,19 @@ class EditAttachmentsForm(wtforms.Form):
'Title') 'Title')
attachment_file = wtforms.FileField( attachment_file = wtforms.FileField(
'File') 'File')
class EditCollectionForm(wtforms.Form):
title = wtforms.TextField(
_('Title'),
[wtforms.validators.Length(min=0, max=500), wtforms.validators.Required(message=_("The title can't be empty"))])
description = wtforms.TextAreaField(
_('Description of this collection'),
description=_("""You can use
<a href="http://daringfireball.net/projects/markdown/basics">
Markdown</a> for formatting."""))
slug = wtforms.TextField(
_('Slug'),
[wtforms.validators.Required(message=_("The slug can't be empty"))],
description=_(
"The title part of this collection's address. "
"You usually don't need to change this."))

View File

@ -22,5 +22,5 @@ edit_routes = [
Route('mediagoblin.edit.profile', '/profile/', Route('mediagoblin.edit.profile', '/profile/',
controller="mediagoblin.edit.views:edit_profile"), controller="mediagoblin.edit.views:edit_profile"),
Route('mediagoblin.edit.account', '/account/', Route('mediagoblin.edit.account', '/account/',
controller="mediagoblin.edit.views:edit_account") controller="mediagoblin.edit.views:edit_account"),
] ]

View File

@ -25,13 +25,14 @@ from mediagoblin import mg_globals
from mediagoblin.auth import lib as auth_lib from mediagoblin.auth import lib as auth_lib
from mediagoblin.edit import forms from mediagoblin.edit import forms
from mediagoblin.edit.lib import may_edit_media from mediagoblin.edit.lib import may_edit_media, may_edit_collection
from mediagoblin.decorators import require_active_login, get_user_media_entry from mediagoblin.decorators import require_active_login, get_user_media_entry, \
user_may_alter_collection, get_user_collection
from mediagoblin.tools.response import render_to_response, redirect from mediagoblin.tools.response import render_to_response, redirect
from mediagoblin.tools.translate import pass_to_ugettext as _ from mediagoblin.tools.translate import pass_to_ugettext as _
from mediagoblin.tools.text import ( from mediagoblin.tools.text import (
convert_to_tag_list_of_dicts, media_tags_as_string) convert_to_tag_list_of_dicts, media_tags_as_string)
from mediagoblin.db.util import check_media_slug_used from mediagoblin.db.util import check_media_slug_used, check_collection_slug_used
import mimetypes import mimetypes
@ -255,3 +256,58 @@ def edit_account(request):
'mediagoblin/edit/edit_account.html', 'mediagoblin/edit/edit_account.html',
{'user': user, {'user': user,
'form': form}) 'form': form})
@require_active_login
@user_may_alter_collection
@get_user_collection
def edit_collection(request, collection):
defaults = dict(
title=collection.title,
slug=collection.slug,
description=collection.description)
form = forms.EditCollectionForm(
request.POST,
**defaults)
if request.method == 'POST' and form.validate():
# Make sure there isn't already a Collection with such a slug
# and userid.
slug_used = check_collection_slug_used(request.db, collection.creator,
request.POST['slug'], collection.id)
# Make sure there isn't already a Collection with this title
existing_collection = request.db.Collection.find_one({
'creator': request.user._id,
'title':request.POST['title']})
if existing_collection and existing_collection.id != collection.id:
messages.add_message(
request, messages.ERROR, _('You already have a collection called "%s"!' % request.POST['title']))
elif slug_used:
form.slug.errors.append(
_(u'A collection with that slug already exists for this user.'))
else:
collection.title = unicode(request.POST['title'])
collection.description = unicode(request.POST.get('description'))
collection.slug = unicode(request.POST['slug'])
collection.save()
return redirect(request, "mediagoblin.user_pages.user_collection",
user=collection.get_creator.username,
collection=collection.slug)
if request.user.is_admin \
and collection.creator != request.user._id \
and request.method != 'POST':
messages.add_message(
request, messages.WARNING,
_("You are editing another user's collection. Proceed with caution."))
return render_to_response(
request,
'mediagoblin/edit/edit_collection.html',
{'collection': collection,
'form': form})

View File

@ -250,6 +250,17 @@ footer {
font-family: 'Lato', sans-serif; font-family: 'Lato', sans-serif;
} }
.button_collect {
background-image: url("../images/icon_collect.png");
background-repeat: no-repeat;
background-position:top center;
height: 30px;
width: 30px;
margin: 0px;
padding: 3px 3px 2px 3px;
position: relative;
}
.pagination { .pagination {
text-align: center; text-align: center;
} }
@ -344,6 +355,10 @@ text-align: center;
text-align: right; text-align: right;
} }
.subform {
margin: 2em;
}
#password_boolean { #password_boolean {
margin-top: 4px; margin-top: 4px;
width: 20px; width: 20px;
@ -416,6 +431,34 @@ textarea#comment_content {
max-height: 135px; max-height: 135px;
} }
/* collection media */
.collection_thumbnail {
float: left;
padding: 0px;
width: 180px;
margin: 0px 4px 10px;
text-align: left;
font-size: 0.875em;
background-color: #222;
border-radius: 0 0 5px 5px;
padding: 0 0 6px;
text-overflow: ellipsis;
}
.collection_thumbnail a {
color: #eee;
text-decoration: none;
}
.collection_thumbnail a.remove {
color: #86D4B1;
}
.collection_thumbnail img {
max-height: 135px;
}
/* media detail */ /* media detail */
h2.media_title { h2.media_title {

View File

@ -41,3 +41,13 @@ class SubmitStartForm(wtforms.Form):
_('License'), _('License'),
[wtforms.validators.Optional(),], [wtforms.validators.Optional(),],
choices=licenses_as_choices()) choices=licenses_as_choices())
class AddCollectionForm(wtforms.Form):
title = wtforms.TextField(
_('Title'),
[wtforms.validators.Length(min=0, max=500), wtforms.validators.Required()])
description = wtforms.TextAreaField(
_('Description of this collection'),
description=_("""You can use
<a href="http://daringfireball.net/projects/markdown/basics">
Markdown</a> for formatting."""))

View File

@ -18,4 +18,7 @@ from routes.route import Route
submit_routes = [ submit_routes = [
Route('mediagoblin.submit.start', '/', Route('mediagoblin.submit.start', '/',
controller='mediagoblin.submit.views:submit_start')] controller='mediagoblin.submit.views:submit_start'),
Route('mediagoblin.submit.collection', '/collection',
controller='mediagoblin.submit.views:add_collection'),
]

View File

@ -14,6 +14,7 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from mediagoblin import messages
import mediagoblin.mg_globals as mg_globals import mediagoblin.mg_globals as mg_globals
import uuid import uuid
from os.path import splitext from os.path import splitext
@ -181,3 +182,46 @@ def submit_start(request):
'mediagoblin/submit/start.html', 'mediagoblin/submit/start.html',
{'submit_form': submit_form, {'submit_form': submit_form,
'app_config': mg_globals.app_config}) 'app_config': mg_globals.app_config})
@require_active_login
def add_collection(request, media=None):
"""
View to create a new collection
"""
submit_form = submit_forms.AddCollectionForm(request.POST)
if request.method == 'POST' and submit_form.validate():
try:
collection = request.db.Collection()
collection.id = ObjectId()
collection.title = unicode(request.POST['title'])
collection.description = unicode(request.POST.get('description'))
collection.creator = request.user._id
collection.generate_slug()
# Make sure this user isn't duplicating an existing collection
existing_collection = request.db.Collection.find_one({
'creator': request.user._id,
'title':collection.title})
if existing_collection:
messages.add_message(
request, messages.ERROR, _('You already have a collection called "%s"!' % collection.title))
else:
collection.save(validate=True)
add_message(request, SUCCESS, _('Collection "%s" added!' % collection.title))
return redirect(request, "mediagoblin.user_pages.user_home",
user=request.user.username)
except Exception as e:
raise
return render_to_response(
request,
'mediagoblin/submit/collection.html',
{'submit_form': submit_form,
'app_config': mg_globals.app_config})

View File

@ -64,6 +64,7 @@
<div class="dropdown_items"> <div class="dropdown_items">
{% if request.user and request.user.status == 'active' %} {% if request.user and request.user.status == 'active' %}
<a href="{{ request.urlgen('mediagoblin.submit.start') }}">{% trans %}+ Add media{% endtrans %}</a> <a href="{{ request.urlgen('mediagoblin.submit.start') }}">{% trans %}+ Add media{% endtrans %}</a>
<a href="{{ request.urlgen('mediagoblin.submit.collection') }}">{% trans %}+ Add collection{% endtrans %}</a>
{% endif %} {% endif %}
<a href="{{ request.urlgen('mediagoblin.user_pages.user_home', user= request.user.username) }}">{% trans %}View your profile{% endtrans %}</a> <a href="{{ request.urlgen('mediagoblin.user_pages.user_home', user= request.user.username) }}">{% trans %}View your profile{% endtrans %}</a>
<a class="button_action" href="{{ request.urlgen('mediagoblin.auth.logout') }}">{% trans %}Log out{% endtrans %}</a> <a class="button_action" href="{{ request.urlgen('mediagoblin.auth.logout') }}">{% trans %}Log out{% endtrans %}</a>

View File

@ -157,6 +157,10 @@
{% include "mediagoblin/utils/tags.html" %} {% include "mediagoblin/utils/tags.html" %}
{% endif %} {% endif %}
{% if media.collections %}
{% include "mediagoblin/utils/collections.html" %}
{% endif %}
{% include "mediagoblin/utils/license.html" %} {% include "mediagoblin/utils/license.html" %}
{% include "mediagoblin/utils/geolocation_map.html" %} {% include "mediagoblin/utils/geolocation_map.html" %}
@ -189,6 +193,15 @@
</p> </p>
{% endif %} {% endif %}
{% if request.user %}
<p>
<a type="submit" href="{{ request.urlgen('mediagoblin.user_pages.media_collect',
user=media.get_uploader.username,
media=media._id) }}" class="button_action button_collect" >
</a>
</p>
{% endif %}
{% block mediagoblin_sidebar %} {% block mediagoblin_sidebar %}
{% endblock %} {% endblock %}
</div> </div>

View File

@ -15,16 +15,32 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import wtforms import wtforms
from wtforms.ext.sqlalchemy.fields import QuerySelectField
from mediagoblin.tools.translate import fake_ugettext_passthrough as _ from mediagoblin.tools.translate import fake_ugettext_passthrough as _
class MediaCommentForm(wtforms.Form): class MediaCommentForm(wtforms.Form):
comment_content = wtforms.TextAreaField( comment_content = wtforms.TextAreaField(
'', '',
[wtforms.validators.Required()]) [wtforms.validators.Required()])
class ConfirmDeleteForm(wtforms.Form): class ConfirmDeleteForm(wtforms.Form):
confirm = wtforms.BooleanField( confirm = wtforms.BooleanField(
_('I am sure I want to delete this')) _('I am sure I want to delete this'))
class ConfirmCollectionItemRemoveForm(wtforms.Form):
confirm = wtforms.BooleanField(
_('I am sure I want to remove this item from the collection'))
class MediaCollectForm(wtforms.Form):
collection = QuerySelectField(allow_blank=True, blank_text=_('-- Select --'), get_label='title',)
note = wtforms.TextAreaField(
_('Include a note'),
[wtforms.validators.Optional()],)
collection_title = wtforms.TextField(
_('Title'),
[wtforms.validators.Length(min=0, max=500)])
collection_description = wtforms.TextAreaField(
_('Description of this collection'),
description=_("""You can use
<a href="http://daringfireball.net/projects/markdown/basics">
Markdown</a> for formatting."""))

View File

@ -40,6 +40,21 @@ user_routes = [
Route('mediagoblin.user_pages.media_post_comment', Route('mediagoblin.user_pages.media_post_comment',
'/{user}/m/{media}/comment/add/', '/{user}/m/{media}/comment/add/',
controller="mediagoblin.user_pages.views:media_post_comment"), controller="mediagoblin.user_pages.views:media_post_comment"),
Route('mediagoblin.user_pages.media_collect',
"/{user}/m/{media}/collect/",
controller="mediagoblin.user_pages.views:media_collect"),
Route('mediagoblin.user_pages.user_collection', "/{user}/collection/{collection}/",
controller="mediagoblin.user_pages.views:user_collection"),
Route('mediagoblin.edit.edit_collection', "/{user}/c/{collection}/edit/",
controller="mediagoblin.edit.views:edit_collection"),
Route('mediagoblin.user_pages.collection_confirm_delete',
"/{user}/c/{collection}/confirm-delete/",
controller="mediagoblin.user_pages.views:collection_confirm_delete"),
Route('mediagoblin.user_pages.collection_item_confirm_remove',
"/{user}/collection/{collection}/{collection_item}/confirm_remove/",
controller="mediagoblin.user_pages.views:collection_item_confirm_remove"),
Route('mediagoblin.user_pages.collection_atom_feed', '/{user}/collection/{collection}/atom/',
controller="mediagoblin.user_pages.views:collection_atom_feed"),
Route('mediagoblin.user_pages.processing_panel', Route('mediagoblin.user_pages.processing_panel',
'/{user}/panel/', '/{user}/panel/',
controller="mediagoblin.user_pages.views:processing_panel"), controller="mediagoblin.user_pages.views:processing_panel"),

View File

@ -28,12 +28,13 @@ from mediagoblin.user_pages import forms as user_forms
from mediagoblin.user_pages.lib import send_comment_email from mediagoblin.user_pages.lib import send_comment_email
from mediagoblin.decorators import (uses_pagination, get_user_media_entry, from mediagoblin.decorators import (uses_pagination, get_user_media_entry,
require_active_login, user_may_delete_media) require_active_login, user_may_delete_media, user_may_alter_collection,
get_user_collection, get_user_collection_item)
from werkzeug.contrib.atom import AtomFeed from werkzeug.contrib.atom import AtomFeed
from mediagoblin.media_types import get_media_manager from mediagoblin.media_types import get_media_manager
from sqlalchemy.exc import IntegrityError
_log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
_log.setLevel(logging.DEBUG) _log.setLevel(logging.DEBUG)
@ -175,6 +176,91 @@ def media_post_comment(request, media):
location=media.url_for_self(request.urlgen)) location=media.url_for_self(request.urlgen))
@get_user_media_entry
@require_active_login
def media_collect(request, media):
form = user_forms.MediaCollectForm(request.POST)
filt = (request.db.Collection.creator == request.user.id)
form.collection.query = request.db.Collection.query.filter(filt).order_by(request.db.Collection.title)
if request.method == 'POST':
if form.validate():
collection = None
collection_item = request.db.CollectionItem()
# If the user is adding a new collection, use that
if request.POST['collection_title']:
collection = request.db.Collection()
collection.id = ObjectId()
collection.title = (
unicode(request.POST['collection_title'])
or unicode(splitext(filename)[0]))
collection.description = unicode(request.POST.get('collection_description'))
collection.creator = request.user._id
collection.generate_slug()
# Make sure this user isn't duplicating an existing collection
existing_collection = request.db.Collection.find_one({
'creator': request.user._id,
'title':collection.title})
if existing_collection:
messages.add_message(
request, messages.ERROR, _('You already have a collection called "%s"!' % collection.title))
return redirect(request, "mediagoblin.user_pages.media_home",
user=request.user.username,
media=media.id)
collection.save(validate=True)
collection_item.collection = collection.id
# Otherwise, use the collection selected from the drop-down
else:
collection = request.db.Collection.find_one({'_id': request.POST.get('collection')})
collection_item.collection = collection.id
# Make sure the user actually selected a collection
if not collection:
messages.add_message(
request, messages.ERROR, _('You have to select or add a collection'))
# Check whether media already exists in collection
elif request.db.CollectionItem.find_one({'media_entry': media.id, 'collection': collection_item.collection}):
messages.add_message(
request, messages.ERROR, _('"%s" already in collection "%s"' % (media.title, collection.title)))
else:
collection_item.media_entry = media.id
collection_item.author = request.user.id
collection_item.note = unicode(request.POST['note'])
collection_item.save(validate=True)
collection.items = collection.items + 1
collection.save(validate=True)
media.collected = media.collected + 1
media.save()
messages.add_message(
request, messages.SUCCESS, _('"%s" added to collection "%s"' % (media.title, collection.title)))
return redirect(request, "mediagoblin.user_pages.media_home",
user=request.user.username,
media=media.id)
else:
messages.add_message(
request, messages.ERROR, _('Please check your entries and try again.'))
return render_to_response(
request,
'mediagoblin/user_pages/media_collect.html',
{'media': media,
'form': form})
@get_user_media_entry @get_user_media_entry
@require_active_login @require_active_login
@user_may_delete_media @user_may_delete_media
@ -227,6 +313,132 @@ def media_confirm_delete(request, media):
'form': form}) 'form': form})
@uses_pagination
def user_collection(request, page):
"""A User-defined Collection"""
user = request.db.User.find_one({
'username': request.matchdict['user'],
'status': u'active'})
if not user:
return render_404(request)
collection = request.db.Collection.find_one(
{'slug': request.matchdict['collection'] })
cursor = request.db.CollectionItem.find(
{'collection': collection.id })
pagination = Pagination(page, cursor)
collection_items = pagination()
#if no data is available, return NotFound
if collection_items == None:
return render_404(request)
return render_to_response(
request,
'mediagoblin/user_pages/collection.html',
{'user': user,
'collection': collection,
'collection_items': collection_items,
'pagination': pagination})
@get_user_collection_item
@require_active_login
@user_may_alter_collection
def collection_item_confirm_remove(request, collection_item):
form = user_forms.ConfirmCollectionItemRemoveForm(request.POST)
if request.method == 'POST' and form.validate():
username = collection_item.in_collection.get_creator.username
collection = collection_item.in_collection
if form.confirm.data is True:
entry = collection_item.get_media_entry
entry.collected = entry.collected - 1
entry.save()
collection_item.delete()
collection.items = collection.items - 1;
collection.save()
messages.add_message(
request, messages.SUCCESS, _('You deleted the item from the collection.'))
else:
messages.add_message(
request, messages.ERROR,
_("The item was not removed because you didn't check that you were sure."))
return redirect(request, "mediagoblin.user_pages.user_collection",
user=username,
collection=collection.slug)
if ((request.user.is_admin and
request.user._id != collection.creator)):
messages.add_message(
request, messages.WARNING,
_("You are about to delete an item from another user's collection. "
"Proceed with caution."))
return render_to_response(
request,
'mediagoblin/user_pages/collection_item_confirm_remove.html',
{'collection_item': collection_item,
'form': form})
@get_user_collection
@require_active_login
@user_may_alter_collection
def collection_confirm_delete(request, collection):
form = user_forms.ConfirmDeleteForm(request.POST)
if request.method == 'POST' and form.validate():
username = collection.get_creator.username
if form.confirm.data is True:
collection_title = collection.title
# Delete all the associated collection items
for item in collection.get_collection_items():
entry = item.get_media_entry
entry.collected = entry.collected - 1
entry.save()
item.delete()
collection.delete()
messages.add_message(
request, messages.SUCCESS, _('You deleted the collection "%s"' % collection_title))
return redirect(request, "mediagoblin.user_pages.user_home",
user=username)
else:
messages.add_message(
request, messages.ERROR,
_("The collection was not deleted because you didn't check that you were sure."))
return redirect(request, "mediagoblin.user_pages.user_collection",
user=username,
collection=collection.slug)
if ((request.user.is_admin and
request.user._id != collection.creator)):
messages.add_message(
request, messages.WARNING,
_("You are about to delete another user's collection. "
"Proceed with caution."))
return render_to_response(
request,
'mediagoblin/user_pages/collection_confirm_delete.html',
{'collection': collection,
'form': form})
ATOM_DEFAULT_NR_OF_UPDATED_ITEMS = 15 ATOM_DEFAULT_NR_OF_UPDATED_ITEMS = 15
@ -293,6 +505,74 @@ def atom_feed(request):
return feed.get_response() return feed.get_response()
def collection_atom_feed(request):
"""
generates the atom feed with the newest images from a collection
"""
user = request.db.User.find_one({
'username': request.matchdict['user'],
'status': u'active'})
if not user:
return render_404(request)
collection = request.db.Collection.find_one({
'creator': user.id,
'slug': request.matchdict['collection']})
cursor = request.db.CollectionItem.find({
'collection': collection._id}) \
.sort('added', DESCENDING) \
.limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS)
"""
ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI)
"""
atomlinks = [{
'href': request.urlgen(
'mediagoblin.user_pages.user_collection',
qualified=True, user=request.matchdict['user'], collection=collection.slug),
'rel': 'alternate',
'type': 'text/html'
}]
if mg_globals.app_config["push_urls"]:
for push_url in mg_globals.app_config["push_urls"]:
atomlinks.append({
'rel': 'hub',
'href': push_url})
feed = AtomFeed(
"MediaGoblin: Feed for %s's collection %s" % (request.matchdict['user'], collection.title),
feed_url=request.url,
id='tag:{host},{year}:collection.user-{user}.title-{title}'.format(
host=request.host,
year=datetime.datetime.today().strftime('%Y'),
user=request.matchdict['user'],
title=collection.title),
links=atomlinks)
for item in cursor:
entry = item.get_media_entry
feed.add(entry.get('title'),
item.note_html,
id=entry.url_for_self(request.urlgen, qualified=True),
content_type='html',
author={
'name': entry.get_uploader.username,
'uri': request.urlgen(
'mediagoblin.user_pages.user_home',
qualified=True, user=entry.get_uploader.username)},
updated=item.get('added'),
links=[{
'href': entry.url_for_self(
request.urlgen,
qualified=True),
'rel': 'alternate',
'type': 'text/html'}])
return feed.get_response()
@require_active_login @require_active_login
def processing_panel(request): def processing_panel(request):