diff options
Diffstat (limited to 'mediagoblin')
23 files changed, 1205 insertions, 5 deletions
diff --git a/mediagoblin/media_types/blog/__init__.py b/mediagoblin/media_types/blog/__init__.py new file mode 100644 index 00000000..83742d0a --- /dev/null +++ b/mediagoblin/media_types/blog/__init__.py @@ -0,0 +1,98 @@ +#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 collection dashboard in case of multiple blogs + ('mediagoblin.media_types.blog.blog_admin_dashboard', + '/u/<string:user>/b/dashboard/', + 'mediagoblin.media_types.blog.views:blog_dashboard' + ), + #blog dashboard + ('mediagoblin.media_types.blog.blog-dashboard', + '/u/<string:user>/b/<string:blog_slug>/dashboard/', + 'mediagoblin.media_types.blog.views:blog_dashboard' + ), + #blog post listing view + ('mediagoblin.media_types.blog.blog_post_listing', + '/u/<string:user>/b/<string:blog_slug>/', + '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 + +hooks = { + 'setup': setup_plugin, + ('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..73ed6060 --- /dev/null +++ b/mediagoblin/media_types/blog/lib.py @@ -0,0 +1,49 @@ +# 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' + +def get_all_blogposts_of_blog(request, blog, state=None): + blog_posts_list = [] + 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 state == None: + blog_posts_list.append(blog_post) + if blog_post.state == state: + blog_posts_list.append(blog_post) + blog_posts_list.reverse() + return blog_posts_list + + diff --git a/mediagoblin/media_types/blog/models.py b/mediagoblin/media_types/blog/models.py new file mode 100644 index 00000000..5a6da19e --- /dev/null +++ b/mediagoblin/media_types/blog/models.py @@ -0,0 +1,68 @@ +# 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.base import Session +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) + + def get_all_blog_posts(self, state=None): + blog_posts = Session.query(MediaEntry).join(BlogPostData)\ + .filter(BlogPostData.blog == self.id) + if state is not None: + blog_posts = blog_posts.filter(MediaEntry.state==state) + return blog_posts + + +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..c553988c --- /dev/null +++ b/mediagoblin/media_types/blog/templates/mediagoblin/blog/blog_admin_dashboard.html @@ -0,0 +1,98 @@ +{# +# 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" %} +{% from "mediagoblin/utils/pagination.html" import render_pagination %} + +{% 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_posts_list.count() %} + <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 %} + {{ render_pagination(request, pagination) }} +{% 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..e1f6ed90 --- /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 as Draft" 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..184693d9 --- /dev/null +++ b/mediagoblin/media_types/blog/templates/mediagoblin/blog/blog_post_listing.html @@ -0,0 +1,50 @@ +{# +# 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 -%} + {% 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> + {{ render_pagination(request, pagination) }} +{% 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/templates/mediagoblin/blog/list_of_blogs.html b/mediagoblin/media_types/blog/templates/mediagoblin/blog/list_of_blogs.html new file mode 100644 index 00000000..a362c7ac --- /dev/null +++ b/mediagoblin/media_types/blog/templates/mediagoblin/blog/list_of_blogs.html @@ -0,0 +1,64 @@ +{# +# 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%} +<style type = "text/css"> + td > a { text-decoration:none; font-weight: bold; } +</style> +{% endblock %} + +{% block mediagoblin_content %} +<table> + {% for blog in blogs %} + {% set my_blog_url = request.urlgen('mediagoblin.media_types.blog.blog-dashboard', + blog_slug=blog.slug, user=request.user.username) %} + {% set others_blog_url = request.urlgen('mediagoblin.media_types.blog.blog_post_listing', + blog_slug=blog.slug, user=url_user) %} + {% if request.user.username == url_user%} + <tr><a href="{{ my_blog_url }}">{{ blog.title }}</a></tr> + {% else %} + <tr><a href="{{ others_blog_url }}">{{ blog.title }}</a></tr> + {% endif %} + <br> + {% endfor %} +</table> +{% endblock mediagoblin_content %} + + + + + + + + + + + + + + + + + + + + + diff --git a/mediagoblin/media_types/blog/views.py b/mediagoblin/media_types/blog/views.py new file mode 100644 index 00000000..3f499044 --- /dev/null +++ b/mediagoblin/media_types/blog/views.py @@ -0,0 +1,292 @@ +# 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, get_all_blogposts_of_blog + +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, uses_pagination) +from mediagoblin.tools.pagination import Pagination +from mediagoblin.tools.response import (render_to_response, + redirect, 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.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 = 4 + 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 = request.db.Blog() + 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() + return redirect(request, "mediagoblin.media_types.blog.blog_admin_dashboard", + user=request.user.username + ) + else: + #the case when max blog count is one. + add_message(request, ERROR, "Welcome! You already have created a blog.") + return redirect(request, "mediagoblin.media_types.blog.blog_admin_dashboard", + user=request.user.username) + + + #Blog already exists. + else: + blog = request.db.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 +@uses_pagination +def blog_dashboard(request, page): + + url_user = request.matchdict.get('user') + user = request.db.User.query.filter_by(username=url_user).one() + blog_slug = request.matchdict.get('blog_slug', None) + + blogs = request.db.Blog.query.filter_by(author=request.user.id) + if blog_slug and user.id == request.user.id: + blog = blogs.filter(Blog.slug==blog_slug).first() + if not blog: + return render_404(request) + blog_posts_list = blog.get_all_blog_posts().order_by(MediaEntry.created.desc()) + _log.info(type(blog_posts_list)) + pagination = Pagination(page, blog_posts_list) + pagination.per_page = 15 + blog_posts_on_a_page = pagination() + + if may_edit_blogpost(request, blog): + return render_to_response( + request, + 'mediagoblin/blog/blog_admin_dashboard.html', + {'blog_posts_list': blog_posts_on_a_page, + 'blog_slug':blog_slug, + 'blog':blog, + 'pagination':pagination + }) + else: + return render_403(request) + else: + blogs = request.db.Blog.query.filter_by(author=user.id) + _log.info(blogs.count()) + return render_to_response( + request, + 'mediagoblin/blog/list_of_blogs.html', + { + 'blogs':blogs, + 'url_user':url_user + }) + + + +#supposed to list all the blog posts belonging to a particular blog of particular user. +@uses_pagination +def blog_post_listing(request, page): + + blog_owner = request.matchdict.get('user') + blog_slug = request.matchdict.get('blog_slug', None) + owner_user = User.query.filter_by(username=blog_owner).one() + blog = request.db.Blog.query.filter_by(slug=blog_slug).first() + + if not owner_user or not blog: + return render_404(request) + + all_blog_posts = blog.get_all_blog_posts(u'processed').order_by(MediaEntry.created.desc()) + pagination = Pagination(page, all_blog_posts) + pagination.per_page = 8 + blog_posts_on_a_page = pagination() + + return render_to_response( + request, + 'mediagoblin/blog/blog_post_listing.html', + {'blog_posts': blog_posts_on_a_page, + 'pagination': pagination, + 'blog_owner': blog_owner + }) + +@require_active_login +def draft_view(request): + blog_slug = request.matchdict.get('blog_slug', None) + blog_post_slug = request.matchdict.get('blog_post_slug', None) + 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 06e0f08f..f5b49f01 100644 --- a/mediagoblin/media_types/image/__init__.py +++ b/mediagoblin/media_types/image/__init__.py @@ -27,6 +27,8 @@ _log = logging.getLogger(__name__) ACCEPTED_EXTENSIONS = ["jpg", "jpeg", "png", "gif", "tiff"] MEDIA_TYPE = 'mediagoblin.media_types.image' +def setup_plugin(): + config = pluginapi.get_config(MEDIA_TYPE) class ImageMediaManager(MediaManagerBase): human_readable = "Image" diff --git a/mediagoblin/processing/__init__.py b/mediagoblin/processing/__init__.py index bdbe0441..9dcdf13c 100644 --- a/mediagoblin/processing/__init__.py +++ b/mediagoblin/processing/__init__.py @@ -392,7 +392,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..327ed8cc 100644 --- a/mediagoblin/routing.py +++ b/mediagoblin/routing.py @@ -37,6 +37,7 @@ def get_url_map(): import mediagoblin.listings.routing import mediagoblin.notifications.routing import mediagoblin.oauth.routing + 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 f9deb2ad..c1a8b494 100644 --- a/mediagoblin/templates/mediagoblin/base.html +++ b/mediagoblin/templates/mediagoblin/base.html @@ -130,6 +130,14 @@ <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 %} Create Blog {% endtrans -%} + </a> + <a class="button_action" href="{{ request.urlgen('mediagoblin.media_types.blog.blog_admin_dashboard', + user=request.user.username) }}"> + {%- trans %} Blog Dashboard {% 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 49691a29..e5646faa 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(): |