diff options
Diffstat (limited to 'mediagoblin')
22 files changed, 1104 insertions, 6 deletions
diff --git a/mediagoblin/media_types/blog/__init__.py b/mediagoblin/media_types/blog/__init__.py new file mode 100644 index 00000000..ec6f68ec --- /dev/null +++ b/mediagoblin/media_types/blog/__init__.py @@ -0,0 +1,97 @@ +#GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import logging +_log = logging.getLogger(__name__) + +from mediagoblin.media_types import MediaManagerBase +from mediagoblin.media_types.blog.models import Blog, BlogPostData + +from mediagoblin.tools import pluginapi + +PLUGIN_DIR = os.path.dirname(__file__) +MEDIA_TYPE = 'mediagoblin.media_types.blogpost' + +def setup_plugin(): + config = pluginapi.get_config(MEDIA_TYPE) + _log.info("setting up blog media type plugin.") + + routes = [ + #blog_create + ('mediagoblin.media_types.blog.create', + '/u/<string:user>/b/create/', + 'mediagoblin.media_types.blog.views:blog_edit' + ), + #blog_edit + ('mediagoblin.media_types.blog.edit', + '/u/<string:user>/b/<string:blog_slug>/edit/', + 'mediagoblin.media_types.blog.views:blog_edit' + ), + #blog post create + ('mediagoblin.media_types.blog.blogpost.create', + '/u/<string:user>/b/<string:blog_slug>/p/create/', + 'mediagoblin.media_types.blog.views:blogpost_create' + ), + #blog post edit + ('mediagoblin.media_types.blog.blogpost.edit', + '/u/<string:user>/b/<string:blog_slug>/p/<string:blog_post_slug>/edit/', + 'mediagoblin.media_types.blog.views:blogpost_edit' + ), + #blog admin dashboard + ('mediagoblin.media_types.blog.blog-dashboard', + '/u/<string:user>/b/<string:blog_slug>/blog_dashboard/', + 'mediagoblin.media_types.blog.views:blog_dashboard' + ), + #blog post listing view + ('mediagoblin.media_types.blog.blog_post_listing', + '/u/<string:user>/b/', + 'mediagoblin.media_types.blog.views:blog_post_listing' + ), + #blog post draft view + ('mediagoblin.media_types.blog.blogpost_draft_view', + '/u/<string:user>/b/<string:blog_slug>/p/<string:blog_post_slug>/draft/', + 'mediagoblin.media_types.blog.views:draft_view') + ] + + + pluginapi.register_routes(routes) + pluginapi.register_template_path(os.path.join(PLUGIN_DIR, 'templates')) + + +class BlogPostMediaManager(MediaManagerBase): + human_readable = "Blog Post" + display_template = "mediagoblin/media_displays/blogpost.html" + default_thumb = "images/media_thumbs/blogpost.jpg" + + def get_blog_by_blogpost(self): + blog_post_data = BlogPostData.query.filter_by(media_entry=self.entry.id).first() + blog = Blog.query.filter_by(id=blog_post_data.blog).first() + return blog + +def get_media_type_and_manager(): + return MEDIA_TYPE, BlogPostMediaManager + +hooks = { + 'setup': setup_plugin, + 'get_media_type_and_manager': get_media_type_and_manager, + ('media_manager', MEDIA_TYPE): lambda: BlogPostMediaManager, +} + + + + + diff --git a/mediagoblin/media_types/blog/config_spec.ini b/mediagoblin/media_types/blog/config_spec.ini new file mode 100644 index 00000000..e0a6844e --- /dev/null +++ b/mediagoblin/media_types/blog/config_spec.ini @@ -0,0 +1,2 @@ +[plugin_spec] +max_blog_count = integer(default=1) diff --git a/mediagoblin/media_types/blog/forms.py b/mediagoblin/media_types/blog/forms.py new file mode 100644 index 00000000..9f595fcf --- /dev/null +++ b/mediagoblin/media_types/blog/forms.py @@ -0,0 +1,42 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import wtforms + +from mediagoblin.tools.text import tag_length_validator, TOO_LONG_TAG_WARNING +from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ +from mediagoblin.tools.licenses import licenses_as_choices + +class BlogPostEditForm(wtforms.Form): + title = wtforms.TextField(_('Title'), + [wtforms.validators.Length(min=0, max=500)]) + description = wtforms.TextAreaField(_('Description')) + tags = wtforms.TextField(_('Tags'), [tag_length_validator], + description="Seperate tags by commas.") + license = wtforms.SelectField(_('License'), + [wtforms.validators.Optional(),], choices=licenses_as_choices()) + +class BlogEditForm(wtforms.Form): + title = wtforms.TextField(_('Title'), + [wtforms.validators.Length(min=0, max=500)]) + description = wtforms.TextAreaField(_('Description')) + + + + + + + diff --git a/mediagoblin/media_types/blog/lib.py b/mediagoblin/media_types/blog/lib.py new file mode 100644 index 00000000..0725b5b6 --- /dev/null +++ b/mediagoblin/media_types/blog/lib.py @@ -0,0 +1,36 @@ +# 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/>. + + +def check_blog_slug_used(author_id, slug, ignore_b_id=None): + from mediagoblin.media_types.blog.models import Blog + query = Blog.query.filter_by(author=author_id, slug=slug) + if ignore_b_id: + query = query.filter(Blog.id != ignore_b_id) + does_exist = query.first() is not None + return does_exist + +def may_edit_blogpost(request, blog): + if request.user.is_admin or request.user.id == blog.author: + return True + return False + +def set_blogpost_state(request, blogpost): + if request.form['status'] == 'Publish': + blogpost.state = u'processed' + else: + blogpost.state = u'failed' + diff --git a/mediagoblin/media_types/blog/models.py b/mediagoblin/media_types/blog/models.py new file mode 100644 index 00000000..a9288c00 --- /dev/null +++ b/mediagoblin/media_types/blog/models.py @@ -0,0 +1,60 @@ +# 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 datetime + +from mediagoblin.db.base import Base +from mediagoblin.db.models import Collection, User, MediaEntry +from mediagoblin.db.mixin import GenerateSlugMixin + +from mediagoblin.media_types.blog.lib import check_blog_slug_used + +from mediagoblin.tools.text import cleaned_markdown_conversion + +from sqlalchemy import ( + Column, Integer, ForeignKey, Unicode, UnicodeText, DateTime) +from sqlalchemy.orm import relationship, backref + + +class BlogMixin(GenerateSlugMixin): + def check_slug_used(self, slug): + return check_blog_slug_used(self.author, slug, self.id) + +class Blog(Base, BlogMixin): + __tablename__ = "mediatype__blogs" + id = Column(Integer, primary_key=True) + title = Column(Unicode) + description = Column(UnicodeText) + author = Column(Integer, ForeignKey(User.id), nullable=False, index=True) #similar to uploader + created = Column(DateTime, nullable=False, default=datetime.datetime.now, index=True) + slug = Column(Unicode) + + +BACKREF_NAME = "blogpost__media_data" + +class BlogPostData(Base): + __tablename__ = "blogpost__mediadata" + + # The primary key *and* reference to the main media_entry + media_entry = Column(Integer, ForeignKey('core__media_entries.id'), primary_key=True) + blog = Column(Integer, ForeignKey('mediatype__blogs.id'), nullable=False) + get_media_entry = relationship("MediaEntry", + backref=backref(BACKREF_NAME, uselist=False, + cascade="all, delete-orphan")) + + +DATA_MODEL = BlogPostData +MODELS = [BlogPostData, Blog] diff --git a/mediagoblin/media_types/blog/templates/mediagoblin/blog/blog_admin_dashboard.html b/mediagoblin/media_types/blog/templates/mediagoblin/blog/blog_admin_dashboard.html new file mode 100644 index 00000000..5ce838bf --- /dev/null +++ b/mediagoblin/media_types/blog/templates/mediagoblin/blog/blog_admin_dashboard.html @@ -0,0 +1,96 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{% extends "mediagoblin/base.html" %} + +{% block title -%} +{{blog.title}} Dashboard — {{ super() }} +{%- endblock title %} + +{% block mediagoblin_head%} +<style type = "text/css"> + td > a { text-decoration:none; font-weight: bold; } +</style> +{% endblock %} + + +{% block mediagoblin_content %} +<h1> {{blog.title}}</h1> +<p> + {{blog.description|safe}} +</p> +<p> + {% set blogpost_create_url = request.urlgen('mediagoblin.media_types.blog.blogpost.create', + blog_slug=blog.slug, + user=request.user.username) %} +<a class="button_action" href="{{ blogpost_create_url }}"> +{%- trans %}Add Blog Post{% endtrans -%} +</a> +· + {% set blog_edit_url = request.urlgen('mediagoblin.media_types.blog.edit', + blog_slug=blog.slug, + user=request.user.username) %} +<a class="button_action" href="{{ blog_edit_url }}"> +{%- trans %}Edit Blog{% endtrans -%} +</a> +</p> +<h2> Blog Post Entries </h2> + {% if blog_post_count!=0 %} + <table class="media_panel processing"> + <tr> + <th>Title</th> + <th>submitted</th> + <th></th> + </tr> + {% for blog_post in blog_posts_list %} + <tr> + {% if blog_post.state == 'processed' %} + <td><a href="{{ blog_post.url_for_self(request.urlgen) }}">{{ blog_post.title }}</a></td> + {% else %} + {% set draft_url = request.urlgen('mediagoblin.media_types.blog.blogpost_draft_view', + blog_slug=blog.slug, user=request.user.username, + blog_post_slug=blog_post.slug) %} + <td><a href="{{ draft_url }}">{{ blog_post.title }}</a></td> + {% endif %} + <td>{{ blog_post.created.strftime("%F %R") }}</td> + + {% if blog_post.state == 'processed' %} + <td><h6><em>Published</em></h6></td> + {% else %} + <td><h6><em>Draft</em></h6></td> + {% endif %} + {% set blogpost_edit_url = request.urlgen('mediagoblin.media_types.blog.blogpost.edit', + blog_slug=blog.slug, user=request.user.username, + blog_post_slug=blog_post.slug) %} + {% set blogpost_delete_url = request.urlgen('mediagoblin.user_pages.media_confirm_delete', + user= blog_post.get_uploader.username, + media_id=blog_post.id) %} + <td> + <a class="button_action" href="{{ blogpost_edit_url }}">{% trans %}Edit{% endtrans %}</a> + <a class="button_action" href="{{ blogpost_delete_url }}">{% trans %}Delete{% endtrans %}</a> + </td> + </tr> + {% endfor %} + </table> + {% else %} + {% trans %} No blog post yet. {% endtrans %} + {% endif %} +{% endblock %} + + + diff --git a/mediagoblin/media_types/blog/templates/mediagoblin/blog/blog_edit_create.html b/mediagoblin/media_types/blog/templates/mediagoblin/blog/blog_edit_create.html new file mode 100644 index 00000000..f4bbcb8f --- /dev/null +++ b/mediagoblin/media_types/blog/templates/mediagoblin/blog/blog_edit_create.html @@ -0,0 +1,41 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_content %} + <form action="" + method="POST" enctype="multipart/form-data"> + <div class="blog_form_box_xl"> + <h1>{% trans %}Create/Edit a Blog{% endtrans %}</h1> + <b>Title</b> + <div class="blog_form_field_input input"> + <h3>{{ form.title}}</h3> + </div> + <b>Description</b> + <div class="blog_form_field_input textarea"> + <h3>{{form.description|safe}}</h3> + </div> + <div class="form_submit_buttons"> + {{ csrf_token }} + <input type="submit" value="{% trans %}Add{% endtrans %}" class="button_form" /> + </div> + </div> + </form> +{% endblock %} diff --git a/mediagoblin/media_types/blog/templates/mediagoblin/blog/blog_post_edit_create.html b/mediagoblin/media_types/blog/templates/mediagoblin/blog/blog_post_edit_create.html new file mode 100644 index 00000000..c5620c56 --- /dev/null +++ b/mediagoblin/media_types/blog/templates/mediagoblin/blog/blog_post_edit_create.html @@ -0,0 +1,53 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block title -%} + {% trans %}Create/Edit a blog post.{% endtrans %} — {{ super() }} +{%- endblock %} + +{% block mediagoblin_content %} + <form action="" method="POST"> + <div class="blog_form_box_xl"> + <h1>{% trans %}Create/Edit a Blog Post.{% endtrans %}</h1> + <b>Title</b> + <div class="blog_form_field_input input"> + <h3>{{ form.title}}</h3> + </div> + <b>Description</b> + <div class="blog_form_field_input textarea"> + <h3>{{form.description|safe}}</h3> + </div> + <b>Tags</b> + <div class="blog_form_field_input input"> + <h3>{{form.tags}}</h3> + </div> + <b>License</b> + <div class="blog_form_field_input input"> + <h3>{{form.license}}</h3> + </div> + <div class="form_submit_buttons"> + {{ csrf_token }} + <input type="submit" name="status" value="Publish" class="button_form"> + <input type="submit" name="status" value="Save" class="button_form"> + </div> + </div> + </form> +{% endblock %} diff --git a/mediagoblin/media_types/blog/templates/mediagoblin/blog/blog_post_listing.html b/mediagoblin/media_types/blog/templates/mediagoblin/blog/blog_post_listing.html new file mode 100644 index 00000000..ea712bb0 --- /dev/null +++ b/mediagoblin/media_types/blog/templates/mediagoblin/blog/blog_post_listing.html @@ -0,0 +1,48 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block title -%} + {% trans %}{{ blog_owner }} 's Blog{% endtrans %} — {{ super() }} +{%- endblock %} + +{% block mediagoblin_head -%} + <style type="text/css"> + h3 {margin = 0; padding = 0; + font-size=.7 em;} + + </style> +{%- endblock %} + +{% block mediagoblin_content %} + <div class="b_list_owner"> <h1><font color="black"> {{ blog_owner }} 's Blog</font></h1></div> + <div> + {% for post in blog_posts %} + <div class="b_listing_title"><a href="{{ post.url_for_self(request.urlgen) }}"> + <h2><font color="black">{{ post.title }}</font></h2></a> + </div> + <h3>{{ post.created.strftime("%d %b, %Y") }}</h3> + + <div class="b_list_des"> <p>{{ post.description|safe }} </p></div> + </br> + </br> + {% endfor %} + </div> +{% endblock %} diff --git a/mediagoblin/media_types/blog/templates/mediagoblin/blog/blogpost_draft_view.html b/mediagoblin/media_types/blog/templates/mediagoblin/blog/blogpost_draft_view.html new file mode 100644 index 00000000..7c634877 --- /dev/null +++ b/mediagoblin/media_types/blog/templates/mediagoblin/blog/blogpost_draft_view.html @@ -0,0 +1,42 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{% extends 'mediagoblin/base.html' %} + +{% block mediagoblin_head %} + {{ super() }} +{% endblock %} + +{% block mediagoblin_content %} +<h1> {{ blogpost.title}}</h1> +<p>{{ blogpost.description|safe}}</p> + +{% set blogpost_edit_url = request.urlgen('mediagoblin.media_types.blog.blogpost.edit', + blog_slug=blog.slug, user=request.user.username, + blog_post_slug=blogpost.slug) %} +{% set blogpost_delete_url = request.urlgen('mediagoblin.user_pages.media_confirm_delete', + user= blogpost.get_uploader.username, + media_id=blogpost.id) %} + <a class="button_action" href="{{ blogpost_edit_url }}">{% trans %}Edit{% endtrans %}</a> + <a class="button_action" href="{{ blogpost_delete_url }}">{% trans %}Delete{% endtrans %}</a> + +{% endblock %} + + + + diff --git a/mediagoblin/media_types/blog/views.py b/mediagoblin/media_types/blog/views.py new file mode 100644 index 00000000..f22d6ac1 --- /dev/null +++ b/mediagoblin/media_types/blog/views.py @@ -0,0 +1,284 @@ +# 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 + +_log = logging.getLogger(__name__) + +from datetime import datetime + +from werkzeug.exceptions import Forbidden + +from mediagoblin import mg_globals + +from mediagoblin.media_types.blog import forms as blog_forms +from mediagoblin.media_types.blog.models import Blog, BlogPostData +from mediagoblin.media_types.blog.lib import may_edit_blogpost, set_blogpost_state + +from mediagoblin.messages import add_message, SUCCESS, ERROR +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, 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, clean_html, + cleaned_markdown_conversion) +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, Collection, MediaEntry + +from mediagoblin.notifications import add_comment_subscription + + +@require_active_login +def blog_edit(request): + """ + View for editing the existing blog or automatically + creating a new blog if user does not have any yet. + """ + url_user = request.matchdict.get('user', None) + blog_slug = request.matchdict.get('blog_slug', None) + + max_blog_count = 1 + form = blog_forms.BlogEditForm(request.form) + # the blog doesn't exists yet + if not blog_slug: + if Blog.query.filter_by(author=request.user.id).count()<max_blog_count: + if request.method=='GET': + return render_to_response( + request, + 'mediagoblin/blog/blog_edit_create.html', + {'form': form, + 'user' : request.user, + 'app_config': mg_globals.app_config}) + + if request.method=='POST' and form.validate(): + _log.info("Here") + blog = Blog() + blog.title = unicode(form.title.data) + blog.description = unicode(cleaned_markdown_conversion((form.description.data))) #remember clean html data. + blog.author = request.user.id + blog.generate_slug() + + blog.save() + return redirect(request, "mediagoblin.media_types.blog.blog-dashboard", + user=request.user.username, + blog_slug=blog.slug) + else: + #the case when max blog count is one. + blog = request.db.Blog.query.filter_by(author=request.user.id).first() + add_message(request, ERROR, "Welcome! You already have created a blog.") + return redirect(request, "mediagoblin.media_types.blog.blog-dashboard", + user=request.user.username, + blog_slug=blog.slug) + + + #Blog already exists. + else: + blog = Blog.query.filter_by(slug=blog_slug).first() + if request.method == 'GET': + defaults = dict( + title = blog.title, + description = cleaned_markdown_conversion(blog.description), + author = request.user.id) + + form = blog_forms.BlogEditForm(**defaults) + + return render_to_response( + request, + 'mediagoblin/blog/blog_edit_create.html', + {'form': form, + 'user': request.user, + 'app_config': mg_globals.app_config}) + else: + if request.method == 'POST' and form.validate(): + blog.title = unicode(form.title.data) + blog.description = unicode(cleaned_markdown_conversion((form.description.data))) + blog.author = request.user.id + blog.generate_slug() + + blog.save() + add_message(request, SUCCESS, "Your blog is updated.") + return redirect(request, "mediagoblin.media_types.blog.blog-dashboard", + user=request.user.username, + blog_slug=blog.slug) + +@require_active_login +def blogpost_create(request): + + form = blog_forms.BlogPostEditForm(request.form, license=request.user.license_preference) + + if request.method == 'POST' and form.validate(): + blog_slug = request.matchdict.get('blog_slug') + blog = request.db.Blog.query.filter_by(slug=blog_slug, + author=request.user.id).first() + if not blog: + return render_404(request) + + blogpost = request.db.MediaEntry() + blogpost.media_type = 'mediagoblin.media_types.blogpost' + blogpost.title = unicode(form.title.data) + blogpost.description = unicode(cleaned_markdown_conversion((form.description.data))) + blogpost.tags = convert_to_tag_list_of_dicts(form.tags.data) + blogpost.license = unicode(form.license.data) or None + blogpost.uploader = request.user.id + blogpost.generate_slug() + + set_blogpost_state(request, blogpost) + blogpost.save() + + # connect this blogpost to its blog + blog_post_data = request.db.BlogPostData() + blog_post_data.blog = blog.id + blog_post_data.media_entry = blogpost.id + blog_post_data.save() + + add_message(request, SUCCESS, _('Woohoo! Submitted!')) + add_comment_subscription(request.user, blogpost) + return redirect(request, "mediagoblin.media_types.blog.blog-dashboard", + user=request.user.username, + blog_slug=blog.slug) + + return render_to_response( + request, + 'mediagoblin/blog/blog_post_edit_create.html', + {'form': form, + 'app_config': mg_globals.app_config, + 'user': request.user.username}) + + +@require_active_login +def blogpost_edit(request): + blog_slug = request.matchdict.get('blog_slug', None) + blog_post_slug = request.matchdict.get('blog_post_slug', None) + + blogpost = request.db.MediaEntry.query.filter_by(slug=blog_post_slug, uploader=request.user.id).first() + blog = request.db.Blog.query.filter_by(slug=blog_slug, author=request.user.id).first() + + if not blogpost or not blog: + return render_404(request) + + defaults = dict( + title = blogpost.title, + description = cleaned_markdown_conversion(blogpost.description), + tags=media_tags_as_string(blogpost.tags), + license=blogpost.license) + + form = blog_forms.BlogPostEditForm(request.form, **defaults) + if request.method == 'POST' and form.validate(): + blogpost.title = unicode(form.title.data) + blogpost.description = unicode(cleaned_markdown_conversion((form.description.data))) + blogpost.tags = convert_to_tag_list_of_dicts(form.tags.data) + blogpost.license = unicode(form.license.data) + set_blogpost_state(request, blogpost) + blogpost.generate_slug() + blogpost.save() + + add_message(request, SUCCESS, _('Woohoo! edited blogpost is submitted')) + return redirect(request, "mediagoblin.media_types.blog.blog-dashboard", + user=request.user.username, + blog_slug=blog.slug) + + return render_to_response( + request, + 'mediagoblin/blog/blog_post_edit_create.html', + {'form': form, + 'app_config': mg_globals.app_config, + 'user': request.user.username, + 'blog_post_slug': blog_post_slug + }) + +@require_active_login +def blog_dashboard(request): + + url_user = request.matchdict.get('user') + blog_posts_list = [] + blog_slug = request.matchdict.get('blog_slug') + _log.info(blog_slug) + + blog = request.db.Blog.query.filter_by(slug=blog_slug).first() + + if not blog: + return render_404(request) + + blog_post_data = request.db.BlogPostData.query.filter_by(blog=blog.id).all() + + for each_blog_post_data in blog_post_data: + blog_post = each_blog_post_data.get_media_entry + if blog_post: + blog_posts_list.append(blog_post) + blog_posts_list.reverse() + blog_post_count = len(blog_posts_list) + + if may_edit_blogpost(request, blog): + return render_to_response( + request, + 'mediagoblin/blog/blog_admin_dashboard.html', + {'blog_posts_list': blog_posts_list, + 'blog_slug':blog_slug, + 'blog':blog, + 'blog_post_count':blog_post_count + }) + +#supposed to list all the blog posts not just belonging to a particular post. +def blog_post_listing(request): + + blog_owner = request.matchdict.get('user') + owner_user = User.query.filter_by(username=blog_owner).one() + + if not owner_user: + return render_404(request) + + all_blog_posts = MediaEntry.query.filter_by( + uploader=owner_user.id, media_type='mediagoblin.media_types.blogpost', + state=u'processed').all() + all_blog_posts.reverse() + _log.info(len(all_blog_posts)) + + return render_to_response( + request, + 'mediagoblin/blog/blog_post_listing.html', + {'blog_posts': all_blog_posts, + 'blog_owner': blog_owner + }) + + +def draft_view(request): + blog_slug = request.matchdict.get('blog_slug') + blog_post_slug = request.matchdict.get('blog_post_slug') + user = request.matchdict.get('user') + + blog = request.db.Blog.query.filter_by(author=request.user.id, slug=blog_slug).first() + blogpost = request.db.MediaEntry.query.filter_by(state = u'failed', uploader=request.user.id, slug=blog_post_slug).first() + + if not blog or not blogpost: + return render_404(request) + + return render_to_response( + request, + 'mediagoblin/blog/blogpost_draft_view.html', + {'blogpost':blogpost, + 'blog': blog + }) + + + + + diff --git a/mediagoblin/media_types/image/__init__.py b/mediagoblin/media_types/image/__init__.py index 1bb9c6f3..bf42e0b3 100644 --- a/mediagoblin/media_types/image/__init__.py +++ b/mediagoblin/media_types/image/__init__.py @@ -26,7 +26,7 @@ MEDIA_TYPE = 'mediagoblin.media_types.image' def setup_plugin(): - config = pluginapi.get_config('mediagoblin.media_types.image') + config = pluginapi.get_config(MEDIA_TYPE) class ImageMediaManager(MediaManagerBase): diff --git a/mediagoblin/processing/__init__.py b/mediagoblin/processing/__init__.py index f3a85940..27d89895 100644 --- a/mediagoblin/processing/__init__.py +++ b/mediagoblin/processing/__init__.py @@ -171,7 +171,7 @@ class BaseProcessingFail(Exception): subclass from. You shouldn't call this itself; instead you should subclass it - and provid the exception_path and general_message applicable to + and provide the exception_path and general_message applicable to this error. """ general_message = u'' diff --git a/mediagoblin/routing.py b/mediagoblin/routing.py index 5961f33b..daf8a45d 100644 --- a/mediagoblin/routing.py +++ b/mediagoblin/routing.py @@ -36,7 +36,10 @@ def get_url_map(): import mediagoblin.webfinger.routing import mediagoblin.listings.routing import mediagoblin.notifications.routing +<<<<<<< HEAD +======= import mediagoblin.oauth.routing +>>>>>>> e7b8059f17c98ee88d933af52b0c4d858e882e8e for route in PluginManager().get_routes(): add_route(*route) diff --git a/mediagoblin/static/css/base.css b/mediagoblin/static/css/base.css index d96b9200..cadd019a 100644 --- a/mediagoblin/static/css/base.css +++ b/mediagoblin/static/css/base.css @@ -288,6 +288,48 @@ text-align: center; max-width: 460px; } +.blog_form_box_xl { + background-color: #222; + border-top: 6px solid #D49086; + padding: 3% 5%; + display: block; + float: none; + width: 90%; + max-width: 800px; + min-height: 500px; + margin-left: auto; + margin-right: auto; +} + +.b_listing_title { + height: 30px; + width: 100%; + padding: 0px; + background-color: #86D4B1; + text-transform:capitalize; + text-decoration: none; + #border-radius: 4px; +} + +.b_listing_title > a { + text-decoration: none; +} + +.b_list_owner { + height: 100px; + width: 100%; + padding: 0em; + margin-right: auto; + background-color: #DDA0DD; + #border-radius: 4px; + text-transform: capitalize; +} + +.b_list_des { + text-align:justify; +} + + .edit_box { border-top: 6px dashed #D49086 } @@ -296,6 +338,10 @@ text-align: center; width: 100%; } +.blog_form_field_input input, .blog_form_field_input textarea { + width: 100%; +} + .form_field_input { margin-bottom: 10px; } diff --git a/mediagoblin/static/extlib/tinymce b/mediagoblin/static/extlib/tinymce new file mode 120000 index 00000000..debf6e2e --- /dev/null +++ b/mediagoblin/static/extlib/tinymce @@ -0,0 +1 @@ +../../../extlib/tinymce
\ No newline at end of file diff --git a/mediagoblin/static/images/media_thumbs/blogpost.jpg b/mediagoblin/static/images/media_thumbs/blogpost.jpg Binary files differnew file mode 100644 index 00000000..c6c68678 --- /dev/null +++ b/mediagoblin/static/images/media_thumbs/blogpost.jpg diff --git a/mediagoblin/templates/mediagoblin/base.html b/mediagoblin/templates/mediagoblin/base.html index 88027472..b1fc658e 100644 --- a/mediagoblin/templates/mediagoblin/base.html +++ b/mediagoblin/templates/mediagoblin/base.html @@ -127,6 +127,10 @@ <a class="button_action" href="{{ request.urlgen('mediagoblin.submit.collection') }}"> {%- trans %}Create new collection{% endtrans -%} </a> + <a class="button_action" href="{{ request.urlgen('mediagoblin.media_types.blog.create', + user=request.user.username) }}"> + {%- trans %} Blog {% endtrans -%} + </a> {% if request.user.is_admin %} <p> <span class="dropdown_title">Admin powers:</span> diff --git a/mediagoblin/templates/mediagoblin/extra_head.html b/mediagoblin/templates/mediagoblin/extra_head.html index 973e2b48..847cfcc7 100644 --- a/mediagoblin/templates/mediagoblin/extra_head.html +++ b/mediagoblin/templates/mediagoblin/extra_head.html @@ -17,3 +17,19 @@ -#} {# Add extra head declarations here for your theme, if appropriate #} + +<script type="text/javascript" + src="{{request.staticdirect('extlib/tinymce/js/tinymce/tinymce.min.js')}}"></script> + + <script type="text/javascript"> + tinyMCE.init({ + selector: "div.blog_form_field_input textarea", + height: 300, + width: 800, + plugins: [ + "advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker", + "searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking", + "save table contextmenu directionality emoticons template paste textcolor" + ] + }) +</script> diff --git a/mediagoblin/templates/mediagoblin/media_displays/blogpost.html b/mediagoblin/templates/mediagoblin/media_displays/blogpost.html new file mode 100644 index 00000000..ca3441b9 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/media_displays/blogpost.html @@ -0,0 +1,33 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{% extends 'mediagoblin/user_pages/blog_media.html' %} + +{% block mediagoblin_head %} + {{ super() }} +{% endblock %} + +{% block mediagoblin_media %} +<h1> {{media.title}}</h1> +<p>{{media.description|safe}}</p> + +{% endblock %} + + + + diff --git a/mediagoblin/templates/mediagoblin/user_pages/blog_media.html b/mediagoblin/templates/mediagoblin/user_pages/blog_media.html new file mode 100644 index 00000000..25dd4783 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/user_pages/blog_media.html @@ -0,0 +1,176 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{%- extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} +{% from "mediagoblin/utils/pagination.html" import render_pagination %} + +{% block title %}{{ media.title }} — {{ super() }}{% endblock %} + +{% block mediagoblin_head %} +<!--[if lte IE 8]><link rel="stylesheet" + href="{{ request.staticdirect('/extlib/leaflet/leaflet.ie.css') }}" /><![endif]--> + <script type="text/javascript" + src="{{ request.staticdirect('/js/comment_show.js') }}"></script> + <script type="text/javascript" + src="{{ request.staticdirect('/js/keyboard_navigation.js') }}"></script> + + {% template_hook("media_head") %} +{% endblock mediagoblin_head %} + +{% block mediagoblin_content %} + <p class="context"> + {%- trans user_url=request.urlgen( + 'mediagoblin.user_pages.user_home', + user=media.get_uploader.username), + username=media.get_uploader.username -%} + ❖ Blog post by <a href="{{user_url}}">{{username}}</a> + {%- endtrans -%} + </p> + {% include "mediagoblin/utils/prev_next.html" %} + <div class="media_pane"> + <div class="media_image_container"> + {% block mediagoblin_media %} + {% set display_media = request.app.public_store.file_url( + media.get_display_media()[1]) %} + {# if there's a medium file size, that means the medium size + # isn't the original... so link to the original! + #} + {% if media.media_files.has_key('medium') %} + <a href="{{ request.app.public_store.file_url( + media.media_files['original']) }}"> + <img class="media_image" + src="{{ display_media }}" + alt="{% trans media_title=media.title -%} + Image for {{ media_title }}{% endtrans %}" /> + </a> + {% else %} + <img class="media_image" + src="{{ display_media }}" + alt="{% trans media_title=media.title -%} + Image for {{ media_title }}{% endtrans %}" /> + {% endif %} + {% endblock %} + </div> + {% if request.user and + (media.uploader == request.user.id or + request.user.is_admin) %} + {% set edit_url = request.urlgen('mediagoblin.media_types.blog.blogpost.edit', + blog_slug=media.media_manager.get_blog_by_blogpost().slug, + user=request.user.username, blog_post_slug=media.slug) %} + <a class="button_action" href="{{ edit_url }}">{% trans %}Edit{% endtrans %}</a> + {% set delete_url = request.urlgen('mediagoblin.user_pages.media_confirm_delete', + user= media.get_uploader.username, + media_id=media.id) %} + <a class="button_action" href="{{ delete_url }}">{% trans %}Delete{% endtrans %}</a> + + {% endif %} + </br> + </br> + {% if comments %} + {% if app_config['allow_comments'] %} + <a + {% if not request.user %} + href="{{ request.urlgen('mediagoblin.auth.login') }}" + {% endif %} + 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', + user= media.get_uploader.username, + media_id=media.id) }}" method="POST" id="form_comment"> + {{ wtforms_util.render_divs(comment_form) }} + <div class="form_submit_buttons"> + <input type="submit" value="{% trans %}Add this comment{% endtrans %}" class="button_action" /> + {{ csrf_token }} + </div> + </form> + {% endif %} + <ul style="list-style:none"> + {% for comment in comments %} + {% set comment_author = comment.get_author %} + <li id="comment-{{ comment.id }}" + {%- if pagination.active_id == comment.id %} + class="comment_wrapper comment_active"> + <a name="comment" id="comment"></a> + {%- else %} + class="comment_wrapper"> + {%- endif %} + <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> + {{ render_pagination(request, pagination, + media.url_for_self(request.urlgen)) }} + {% endif %} + </div> + <div class="media_sidebar"> + <h3>{% trans %}Added{% endtrans %}</h3> + <p><span title="{{ media.created.strftime("%I:%M%p %Y-%m-%d") }}"> + {%- trans formatted_time=timesince(media.created) -%} + {{ formatted_time }} ago + {%- endtrans -%} + </span></p> + + {% block mediagoblin_after_added_sidebar %} + {% endblock %} + + {% if media.tags %} + {% include "mediagoblin/utils/tags.html" %} + {% endif %} + + {% include "mediagoblin/utils/collections.html" %} + + {% include "mediagoblin/utils/license.html" %} + + {% include "mediagoblin/utils/exif.html" %} + + {% template_hook("media_sideinfo") %} + + {% block mediagoblin_sidebar %} + {% endblock %} + + </div> + <div class="clear"></div> +{% endblock %} diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index 9d3cb08b..87d0034e 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -286,11 +286,29 @@ def media_collect(request, media): #TODO: Why does @user_may_delete_media not implicate @require_active_login? -@get_media_entry_by_id -@require_active_login -@user_may_delete_media -def media_confirm_delete(request, media): +@require_active_login +def media_confirm_delete(request): + + allowed_state = [u'failed', u'processed'] + media = None + for media_state in allowed_state: + media = request.db.MediaEntry.query.filter_by(id=request.matchdict['media_id'], state=media_state).first() + if media: + break + + if not media: + return render_404(request) + + given_username = request.matchdict.get('user') + if given_username and (given_username != media.get_uploader.username): + return render_404(request) + + uploader_id = media.uploader + if not (request.user.is_admin or + request.user.id == uploader_id): + raise Forbidden() + form = user_forms.ConfirmDeleteForm(request.form) if request.method == 'POST' and form.validate(): |