def post_login(self, came_from=None, **kwargs): if not request.identity: # The FriendlyForm plugin will always issue a redirect to # /login/continue (post login url) even for failed logins. # If 'came_from' is a protected page (i.e. /admin) we could just # redirect there and the login form will be displayed again with # our login error message. # However if the user tried to login from the front page, this # mechanism doesn't work so go to the login method directly here. self._increase_number_of_failed_logins() return self.login(came_from=came_from) if came_from: url_mapper = request.environ['routes.url'].mapper target = dispatch_info_for_url(came_from, url_mapper) if not is_url_for_mediadrop_domain(came_from): log.debug('no redirect to %r because target url does match our hostname (prevents parameter base redirection attacks)' % came_from) came_from = None elif (target is not None) and getattr(target.action, '_request_method', None) not in ('GET', None): log.debug('no redirect to %r because target url does not allow GET requests' % came_from) came_from = None if came_from: redirect(came_from) # It is important to return absolute URLs (if app mounted in subdirectory) if request.perm.contains_permission(u'edit') or request.perm.contains_permission(u'admin'): redirect(url_for('/admin', qualified=True)) redirect(url_for('/', qualified=True))
def view(self, slug, podcast_slug=None, **kwargs): """Display the media player, info and comments. :param slug: The :attr:`~mediadrop.models.media.Media.slug` to lookup :param podcast_slug: The :attr:`~mediadrop.models.podcasts.Podcast.slug` for podcast this media belongs to. Although not necessary for looking up the media, it tells us that the podcast slug was specified in the URL and therefore we reached this action by the preferred route. :rtype dict: :returns: media The :class:`~mediadrop.model.media.Media` instance for display. related_media A list of :class:`~mediadrop.model.media.Media` instances that rank as topically related to the given media item. comments A list of :class:`~mediadrop.model.comments.Comment` instances associated with the selected media item. comment_form_action ``str`` comment form action comment_form_values ``dict`` form values next_episode The next episode in the podcast series, if this media belongs to a podcast, another :class:`~mediadrop.model.media.Media` instance. """ media = fetch_row(Media, slug=slug) request.perm.assert_permission(u'view', media.resource) if media.podcast_id is not None: # Always view podcast media from a URL that shows the context of the podcast if url_for() != url_for(podcast_slug=media.podcast.slug): redirect(podcast_slug=media.podcast.slug) try: media.increment_views() DBSession.commit() except OperationalError: DBSession.rollback() if request.settings['comments_engine'] == 'facebook': response.facebook = Facebook(request.settings['facebook_appid']) related_media = viewable_media(Media.query.related(media))[:6] # TODO: finish implementation of different 'likes' buttons # e.g. the default one, plus a setting to use facebook. return dict( media = media, related_media = related_media, comments = media.comments.published().all(), comment_form_action = url_for(action='comment'), comment_form_values = kwargs, )
def index(self, page=1, show='latest', q=None, tag=None, **kwargs): """List media with pagination. The media paginator may be accessed in the template with :attr:`c.paginators.media`, see :class:`webhelpers.paginate.Page`. :param page: Page number, defaults to 1. :type page: int :param show: 'latest', 'popular' or 'featured' :type show: unicode or None :param q: A search query to filter by :type q: unicode or None :param tag: A tag slug to filter for :type tag: unicode or None :rtype: dict :returns: media The list of :class:`~mediadrop.model.media.Media` instances for this page. result_count The total number of media items for this query search_query The query the user searched for, if any """ media = Media.query.published() media, show = helpers.filter_library_controls(media, show) if q: media = media.search(q, bool=True) if tag: tag = fetch_row(Tag, slug=tag) media = media.filter(Media.tags.contains(tag)) if (request.settings['rss_display'] == 'True') and (not (q or tag)): if show == 'latest': response.feed_links.extend([ (url_for(controller='/sitemaps', action='latest'), _(u'Latest RSS')), ]) elif show == 'featured': response.feed_links.extend([ (url_for(controller='/sitemaps', action='featured'), _(u'Featured RSS')), ]) media = viewable_media(media) return dict( media = media, result_count = media.count(), search_query = q, show = show, tag = tag, )
def save(self, id, slug, title, author_name, author_email, description, notes, podcast, tags, categories, delete=None, **kwargs): """Save changes or create a new :class:`~mediadrop.model.media.Media` instance. Form handler the :meth:`edit` action and the :class:`~mediadrop.forms.admin.media.MediaForm`. Redirects back to :meth:`edit` after successful editing and :meth:`index` after successful deletion. """ media = fetch_row(Media, id) if delete: self._delete_media(media) redirect(action='index', id=None) if not slug: slug = slugify(title) elif slug.startswith('_stub_'): slug = slug[len('_stub_'):] if slug != media.slug: media.slug = get_available_slug(Media, slug, media) media.title = title media.author = Author(author_name, author_email) media.description = description media.notes = notes media.podcast_id = podcast media.set_tags(tags) media.set_categories(categories) media.update_status() DBSession.add(media) DBSession.flush() if id == 'new' and not has_thumbs(media): create_default_thumbs_for(media) if request.is_xhr: status_form_xhtml = unicode(update_status_form.display( action=url_for(action='update_status', id=media.id), media=media)) return dict( media_id = media.id, values = {'slug': slug}, link = url_for(action='edit', id=media.id), status_form = status_form_xhtml, ) else: redirect(action='edit', id=media.id)
def explore(self, **kwargs): """Display the most recent 15 media. :rtype: Dict :returns: latest Latest media popular Latest media """ media = Media.query.published() latest = media.order_by(Media.publish_on.desc()) popular = media.order_by(Media.popularity_points.desc()) featured = None is_featured_item_enabled = request.settings['appearance_enable_featured_items'] or request.settings['appearance_enable_cooliris'] if is_featured_item_enabled: featured_cat = helpers.get_featured_category() if featured_cat: featured = viewable_media(latest.in_category(featured_cat)).first() if not featured: featured = viewable_media(popular).first() nr_latest_items = 8 if is_featured_item_enabled: nr_popular_items = max(nr_latest_items - 3, 0) else: nr_popular_items = nr_latest_items popular = viewable_media(popular.exclude(featured))[:nr_popular_items] latest = viewable_media(latest.exclude(featured, popular))[:nr_latest_items] if request.settings['sitemaps_display'] == 'True': response.feed_links.extend([ (url_for(controller='/sitemaps', action='google'), _(u'Sitemap XML')), (url_for(controller='/sitemaps', action='mrss'), _(u'Sitemap RSS')), ]) if request.settings['rss_display'] == 'True': response.feed_links.extend([ (url_for(controller='/sitemaps', action='latest'), _(u'Latest RSS')), ]) return dict( featured = featured, latest = latest, popular = popular, categories = Category.query.populated_tree(), )
def edit(self, id, **kwargs): """Display the :class:`~mediadrop.forms.admin.groups.GroupForm` for editing or adding. :param id: Group ID :type id: ``int`` or ``"new"`` :rtype: dict :returns: user The :class:`~mediadrop.model.auth.Group` instance we're editing. user_form The :class:`~mediadrop.forms.admin.groups.GroupForm` instance. user_action ``str`` form submit url group_values ``dict`` form values """ group = fetch_row(Group, id) if tmpl_context.action == 'save' or id == 'new': # Use the values from error_handler or GET for new groups group_values = kwargs else: permission_ids = map(lambda permission: permission.permission_id, group.permissions) group_values = dict(display_name=group.display_name, group_name=group.group_name, permissions=permission_ids) return dict( group=group, group_form=group_form, group_action=url_for(action='save'), group_values=group_values, )
def submit_async(self, **kwargs): """Ajax form validation and/or submission. This is the save handler for :class:`~mediadrop.forms.media.UploadForm`. When ajax is enabled this action is called for each field as the user fills them in. Although the entire form is validated, the JS only provides the value of one field at a time, :param validate: A JSON list of field names to check for validation :parma \*\*kwargs: One or more form field values. :rtype: JSON dict :returns: :When validating one or more fields: valid bool err A dict of error messages keyed by the field names :When saving an upload: success bool redirect If valid, the redirect url for the upload successful page. """ if 'validate' in kwargs: # we're just validating the fields. no need to worry. fields = json.loads(kwargs['validate']) err = {} for field in fields: if field in tmpl_context.form_errors: err[field] = tmpl_context.form_errors[field] data = dict(valid=len(err) == 0, err=err) else: # We're actually supposed to save the fields. Let's do it. if len(tmpl_context.form_errors) != 0: # if the form wasn't valid, return failure tmpl_context.form_errors['success'] = False data = tmpl_context.form_errors else: # else actually save it! kwargs.setdefault('name') media_obj = self.save_media_obj( kwargs['name'], kwargs['email'], kwargs['title'], kwargs['description'], None, kwargs['file'], kwargs['url'], ) email.send_media_notification(media_obj) data = dict(success=True, redirect=url_for(action='success')) return data
def update_status(self, id, status=None, publish_on=None, publish_until=None, **values): """Update the publish status for the given media. :param id: Media ID :type id: ``int`` :param update_status: The text of the submit button which indicates that the :attr:`~mediadrop.model.media.Media.status` should change. :type update_status: ``unicode`` or ``None`` :param publish_on: A date to set to :attr:`~mediadrop.model.media.Media.publish_on` :type publish_on: :class:`datetime.datetime` or ``None`` :param publish_until: A date to set to :attr:`~mediadrop.model.media.Media.publish_until` :type publish_until: :class:`datetime.datetime` or ``None`` :rtype: JSON dict :returns: success bool message Error message, if unsuccessful status_form Rendered XHTML for the status form, updated to reflect the changes made. """ media = fetch_row(Media, id) new_slug = None # Make the requested change assuming it will be allowed if status == 'unreviewed': media.reviewed = True elif status == 'draft': self._publish_media(media, publish_on) elif publish_on: media.publish_on = publish_on media.update_popularity() elif publish_until: media.publish_until = publish_until # Verify the change is valid by re-determining the status media.update_status() DBSession.flush() if request.is_xhr: # Return the rendered widget for injection status_form_xhtml = unicode( update_status_form.display( action=url_for(action='update_status'), media=media)) return dict( success=True, status_form=status_form_xhtml, slug=new_slug, ) else: redirect(action='edit')
def tindex(self, **kwargs): """Display the upload form. :rtype: Dict :returns: legal_wording XHTML legal wording for rendering support_email An help contact address upload_form The :class:`~mediadrop.forms.uploader.UploadForm` instance form_values ``dict`` form values, if any """ support_emails = request.settings['email_support_requests'] support_emails = email.parse_email_string(support_emails) support_email = support_emails and support_emails[0] or None return dict( legal_wording = request.settings['wording_user_uploads'], support_email = support_email, upload_form = UploadForm( action = url_for(controller='/upload', action='tsubmit'), ), form_values = kwargs, )
def edit(self, id, **kwargs): """Display the :class:`~mediadrop.forms.admin.groups.GroupForm` for editing or adding. :param id: Group ID :type id: ``int`` or ``"new"`` :rtype: dict :returns: user The :class:`~mediadrop.model.auth.Group` instance we're editing. user_form The :class:`~mediadrop.forms.admin.groups.GroupForm` instance. user_action ``str`` form submit url group_values ``dict`` form values """ group = fetch_row(Group, id) if tmpl_context.action == "save" or id == "new": # Use the values from error_handler or GET for new groups group_values = kwargs else: permission_ids = map(lambda permission: permission.permission_id, group.permissions) group_values = dict( display_name=group.display_name, group_name=group.group_name, permissions=permission_ids ) return dict(group=group, group_form=group_form, group_action=url_for(action="save"), group_values=group_values)
def view(self, slug, page=1, show='latest', **kwargs): """View a podcast and the media that belongs to it. :param slug: A :attr:`~mediadrop.model.podcasts.Podcast.slug` :param page: Page number, defaults to 1. :type page: int :rtype: dict :returns: podcast A :class:`~mediadrop.model.podcasts.Podcast` instance. episodes A list of :class:`~mediadrop.model.media.Media` instances that belong to the ``podcast``. podcasts A list of all the other podcasts """ podcast = fetch_row(Podcast, slug=slug) episodes = podcast.media.published() episodes, show = helpers.filter_library_controls(episodes, show) episodes = viewable_media(episodes) if request.settings['rss_display'] == 'True': response.feed_links.append( (url_for(action='feed'), podcast.title) ) return dict( podcast = podcast, episodes = episodes, result_count = episodes.count(), show = show, )
def login(self, came_from=None, **kwargs): if request.environ.get('repoze.who.identity'): redirect(came_from or '/') # the friendlyform plugin requires that these values are set in the # query string form_url = url_for('/login/submit', came_from=(came_from or '').encode('utf-8'), __logins=str(self._is_failed_login())) login_errors = None if self._is_failed_login(): login_errors = Invalid('dummy', None, {}, error_dict={ '_form': Invalid( _('Invalid username or password.'), None, {}), 'login': Invalid('dummy', None, {}), 'password': Invalid('dummy', None, {}), }) return dict( login_form=login_form, form_action=form_url, form_values=kwargs, login_errors=login_errors, )
def view(self, slug, page=1, show='latest', **kwargs): """View a podcast and the media that belongs to it. :param slug: A :attr:`~mediadrop.model.podcasts.Podcast.slug` :param page: Page number, defaults to 1. :type page: int :rtype: dict :returns: podcast A :class:`~mediadrop.model.podcasts.Podcast` instance. episodes A list of :class:`~mediadrop.model.media.Media` instances that belong to the ``podcast``. podcasts A list of all the other podcasts """ podcast = fetch_row(Podcast, slug=slug) episodes = podcast.media.published() episodes, show = helpers.filter_library_controls(episodes, show) episodes = viewable_media(episodes) if request.settings['rss_display'] == 'True': response.feed_links.append((url_for(action='feed'), podcast.title)) return dict( podcast=podcast, episodes=episodes, result_count=episodes.count(), show=show, )
def submit_async(self, **kwargs): """Ajax form validation and/or submission. This is the save handler for :class:`~mediadrop.forms.media.UploadForm`. When ajax is enabled this action is called for each field as the user fills them in. Although the entire form is validated, the JS only provides the value of one field at a time, :param validate: A JSON list of field names to check for validation :parma \*\*kwargs: One or more form field values. :rtype: JSON dict :returns: :When validating one or more fields: valid bool err A dict of error messages keyed by the field names :When saving an upload: success bool redirect If valid, the redirect url for the upload successful page. """ if "validate" in kwargs: # we're just validating the fields. no need to worry. fields = json.loads(kwargs["validate"]) err = {} for field in fields: if field in tmpl_context.form_errors: err[field] = tmpl_context.form_errors[field] data = dict(valid=len(err) == 0, err=err) else: # We're actually supposed to save the fields. Let's do it. if len(tmpl_context.form_errors) != 0: # if the form wasn't valid, return failure tmpl_context.form_errors["success"] = False data = tmpl_context.form_errors else: # else actually save it! kwargs.setdefault("name") media_obj = self.save_media_obj( kwargs["name"], kwargs["email"], kwargs["title"], kwargs["description"], None, kwargs["file"], kwargs["url"], ) email.send_media_notification(media_obj) data = dict(success=True, redirect=url_for(action="success")) return data
def save(self, id, delete=None, **kwargs): """Save changes or create a category. See :class:`~mediadrop.forms.admin.settings.categories.CategoryForm` for POST vars. :param id: Category ID :param delete: If true the category is to be deleted rather than saved. :type delete: bool :rtype: JSON dict :returns: success bool """ if tmpl_context.form_errors: if request.is_xhr: return dict(success=False, errors=tmpl_context.form_errors) else: # TODO: Add error reporting for users with JS disabled? return redirect(action="edit") cat = fetch_row(Category, id) if delete: DBSession.delete(cat) data = dict(success=True, id=cat.id, parent_options=unicode(category_form.c["parent_id"].display())) else: cat.name = kwargs["name"] cat.slug = get_available_slug(Category, kwargs["slug"], cat) if kwargs["parent_id"]: parent = fetch_row(Category, kwargs["parent_id"]) if parent is not cat and cat not in parent.ancestors(): cat.parent = parent else: cat.parent = None DBSession.add(cat) DBSession.flush() data = dict( success=True, id=cat.id, name=cat.name, slug=cat.slug, parent_id=cat.parent_id, parent_options=unicode(category_form.c["parent_id"].display()), depth=cat.depth(), row=unicode( category_row_form.display( action=url_for(id=cat.id), category=cat, depth=cat.depth(), first_child=True ) ), ) if request.is_xhr: return data else: redirect(action="index", id=None)
def post_login(self, came_from=None, **kwargs): if not request.identity: # The FriendlyForm plugin will always issue a redirect to # /login/continue (post login url) even for failed logins. # If 'came_from' is a protected page (i.e. /admin) we could just # redirect there and the login form will be displayed again with # our login error message. # However if the user tried to login from the front page, this # mechanism doesn't work so go to the login method directly here. self._increase_number_of_failed_logins() return self.login(came_from=came_from) if came_from: redirect(came_from) # It is important to return absolute URLs (if app mounted in subdirectory) if request.perm.contains_permission(u'edit') or request.perm.contains_permission(u'admin'): redirect(url_for('/admin', qualified=True)) redirect(url_for('/', qualified=True))
def post_login(self, came_from=None, **kwargs): if not request.identity: # The FriendlyForm plugin will always issue a redirect to # /login/continue (post login url) even for failed logins. # If 'came_from' is a protected page (i.e. /admin) we could just # redirect there and the login form will be displayed again with # our login error message. # However if the user tried to login from the front page, this # mechanism doesn't work so go to the login method directly here. self._increase_number_of_failed_logins() return self.login(came_from=came_from) if came_from: redirect(came_from) # It is important to return absolute URLs (if app mounted in subdirectory) if request.perm.contains_permission( u'edit') or request.perm.contains_permission(u'admin'): redirect(url_for('/admin', qualified=True)) redirect(url_for('/', qualified=True))
def explore(self, **kwargs): """Display the most recent 15 media. :rtype: Dict :returns: latest Latest media popular Latest media """ media = Media.query.published() latest = media.order_by(Media.publish_on.desc()) popular = media.order_by(Media.popularity_points.desc()) featured = None featured_cat = helpers.get_featured_category() if featured_cat: featured = viewable_media(latest.in_category(featured_cat)).first() if not featured: featured = viewable_media(popular).first() latest = viewable_media(latest.exclude(featured))[:8] popular = viewable_media(popular.exclude(featured, latest))[:5] if request.settings['sitemaps_display'] == 'True': response.feed_links.extend([ (url_for(controller='/sitemaps', action='google'), _(u'Sitemap XML')), (url_for(controller='/sitemaps', action='mrss'), _(u'Sitemap RSS')), ]) if request.settings['rss_display'] == 'True': response.feed_links.extend([ (url_for(controller='/sitemaps', action='latest'), _(u'Latest RSS')), ]) return dict( featured=featured, latest=latest, popular=popular, categories=Category.query.populated_tree(), )
def google(self, page=None, limit=10000, **kwargs): """Generate a sitemap which contains googles Video Sitemap information. This action may return a <sitemapindex> or a <urlset>, depending on how many media items are in the database, and the values of the page and limit params. :param page: Page number, defaults to 1. :type page: int :param page: max records to display on page, defaults to 10000. :type page: int """ if request.settings['sitemaps_display'] != 'True': abort(404) response.content_type = \ content_type_for_response(['application/xml', 'text/xml']) media = viewable_media(Media.query.published()) if page is None: if media.count() > limit: return dict(pages=math.ceil(media.count() / float(limit))) else: page = int(page) media = media.offset(page * limit).limit(limit) if page: links = [] else: links = [ url_for(controller='/', qualified=True), url_for(controller='/media', show='popular', qualified=True), url_for(controller='/media', show='latest', qualified=True), url_for(controller='/categories', qualified=True), ] return dict( media = media, page = page, links = links, )
def update_status(self, id, status=None, publish_on=None, publish_until=None, **values): """Update the publish status for the given media. :param id: Media ID :type id: ``int`` :param update_status: The text of the submit button which indicates that the :attr:`~mediadrop.model.media.Media.status` should change. :type update_status: ``unicode`` or ``None`` :param publish_on: A date to set to :attr:`~mediadrop.model.media.Media.publish_on` :type publish_on: :class:`datetime.datetime` or ``None`` :param publish_until: A date to set to :attr:`~mediadrop.model.media.Media.publish_until` :type publish_until: :class:`datetime.datetime` or ``None`` :rtype: JSON dict :returns: success bool message Error message, if unsuccessful status_form Rendered XHTML for the status form, updated to reflect the changes made. """ media = fetch_row(Media, id) new_slug = None # Make the requested change assuming it will be allowed if status == 'unreviewed': media.reviewed = True elif status == 'draft': self._publish_media(media, publish_on) elif publish_on: media.publish_on = publish_on media.update_popularity() elif publish_until: media.publish_until = publish_until # Verify the change is valid by re-determining the status media.update_status() DBSession.flush() if request.is_xhr: # Return the rendered widget for injection status_form_xhtml = unicode(update_status_form.display( action=url_for(action='update_status'), media=media)) return dict( success = True, status_form = status_form_xhtml, slug = new_slug, ) else: redirect(action='edit')
def edit(self, id, engine_type=None, **kwargs): """Display the :class:`~mediadrop.lib.storage.StorageEngine` for editing or adding. :param id: Storage ID :type id: ``int`` or ``"new"`` :rtype: dict :returns: """ engine = self.fetch_engine(id, engine_type) return { 'engine': engine, 'form': engine.settings_form, 'form_action': url_for(action='save', engine_type=engine_type), 'form_values': kwargs, }
def edit(self, id, name=None, **kwargs): """Display the :class:`~mediadrop.model.players.PlayerPrefs` for editing or adding. :param id: PlayerPrefs ID :type id: ``int`` or ``"new"`` :rtype: dict :returns: """ playerp = fetch_row(PlayerPrefs, id) return { 'player': playerp, 'form': playerp.settings_form, 'form_action': url_for(action='save'), 'form_values': kwargs, }
def edit(self, id, **kwargs): """Display the :class:`~mediadrop.forms.admin.users.UserForm` for editing or adding. :param id: User ID :type id: ``int`` or ``"new"`` :rtype: dict :returns: user The :class:`~mediadrop.model.auth.User` instance we're editing. user_form The :class:`~mediadrop.forms.admin.users.UserForm` instance. user_action ``str`` form submit url user_values ``dict`` form values """ user = fetch_row(User, id) if tmpl_context.action == 'save' or id == 'new': # Use the values from error_handler or GET for new users user_values = kwargs user_values['login_details.password'] = None user_values['login_details.confirm_password'] = None else: group_ids = None if user.groups: group_ids = map(lambda group: group.group_id, user.groups) user_values = dict( display_name=user.display_name, email_address=user.email_address, login_details=dict( groups=group_ids, user_name=user.user_name, ), ) return dict( user=user, user_form=user_form, user_action=url_for(action='save'), user_values=user_values, )
def edit(self, id, **kwargs): """Display the :class:`~mediadrop.forms.admin.users.UserForm` for editing or adding. :param id: User ID :type id: ``int`` or ``"new"`` :rtype: dict :returns: user The :class:`~mediadrop.model.auth.User` instance we're editing. user_form The :class:`~mediadrop.forms.admin.users.UserForm` instance. user_action ``str`` form submit url user_values ``dict`` form values """ user = fetch_row(User, id) if tmpl_context.action == 'save' or id == 'new': # Use the values from error_handler or GET for new users user_values = kwargs user_values['login_details.password'] = None user_values['login_details.confirm_password'] = None else: group_ids = None if user.groups: group_ids = map(lambda group: group.group_id, user.groups) user_values = dict( display_name = user.display_name, email_address = user.email_address, login_details = dict( groups = group_ids, user_name = user.user_name, ), ) return dict( user = user, user_form = user_form, user_action = url_for(action='save'), user_values = user_values, )
def index(self, slug=None, **kwargs): media = Media.query.published() if c.category: media = media.in_category(c.category) response.feed_links.append( ( url_for(controller="/categories", action="feed", slug=c.category.slug), _("Latest media in %s") % c.category.name, ) ) latest = media.order_by(Media.publish_on.desc()) popular = media.order_by(Media.popularity_points.desc()) latest = viewable_media(latest)[:5] popular = viewable_media(popular.exclude(latest))[:5] return dict(latest=latest, popular=popular)
def send_media_notification(media_obj): """ Send a creation notification email that a new Media object has been created. Sends to the address configured in the 'email_media_uploaded' address, if one has been created. :param media_obj: The media object to send a notification about. :type media_obj: :class:`~mediadrop.model.media.Media` instance """ send_to = request.settings['email_media_uploaded'] if not send_to: # media notification emails are disabled! return edit_url = url_for(controller='/admin/media', action='edit', id=media_obj.id, qualified=True) clean_description = strip_xhtml( line_break_xhtml(line_break_xhtml(media_obj.description))) type = media_obj.type title = media_obj.title author_name = media_obj.author.name author_email = media_obj.author.email subject = _('New %(type)s: %(title)s') % locals() body = _("""A new %(type)s file has been uploaded! Title: %(title)s Author: %(author_name)s (%(author_email)s) Admin URL: %(edit_url)s Description: %(clean_description)s """) % locals() send(send_to, request.settings['email_send_from'], subject, body)
def index(self, slug=None, **kwargs): media = Media.query.published() if c.category: media = media.in_category(c.category) response.feed_links.append( (url_for(controller='/categories', action='feed', slug=c.category.slug), _('Latest media in %s') % c.category.name)) latest = media.order_by(Media.publish_on.desc()) popular = media.order_by(Media.popularity_points.desc()) latest = viewable_media(latest)[:5] popular = viewable_media(popular.exclude(latest))[:5] return dict( latest=latest, popular=popular, )
def send_media_notification(media_obj): """ Send a creation notification email that a new Media object has been created. Sends to the address configured in the 'email_media_uploaded' address, if one has been created. :param media_obj: The media object to send a notification about. :type media_obj: :class:`~mediadrop.model.media.Media` instance """ send_to = request.settings['email_media_uploaded'] if not send_to: # media notification emails are disabled! return edit_url = url_for(controller='/admin/media', action='edit', id=media_obj.id, qualified=True) clean_description = strip_xhtml(line_break_xhtml(line_break_xhtml(media_obj.description))) type = media_obj.type title = media_obj.title author_name = media_obj.author.name author_email = media_obj.author.email subject = _('New %(type)s: %(title)s') % locals() body = _("""A new %(type)s file has been uploaded! Title: %(title)s Author: %(author_name)s (%(author_email)s) Admin URL: %(edit_url)s Description: %(clean_description)s """) % locals() send(send_to, request.settings['email_send_from'], subject, body)
def login(self, came_from=None, **kwargs): if request.environ.get('repoze.who.identity'): redirect(came_from or '/') # the friendlyform plugin requires that these values are set in the # query string form_url = url_for('/login/submit', came_from=(came_from or '').encode('utf-8'), __logins=str(self._is_failed_login())) login_errors = None if self._is_failed_login(): login_errors = Invalid('dummy', None, {}, error_dict={ '_form': Invalid(_('Invalid username or password.'), None, {}), 'login': Invalid('dummy', None, {}), 'password': Invalid('dummy', None, {}), }) return dict( login_form = login_form, form_action = form_url, form_values = kwargs, login_errors = login_errors, )
def comment(self, slug, name='', email=None, body='', **kwargs): """Post a comment from :class:`~mediadrop.forms.comments.PostCommentForm`. :param slug: The media :attr:`~mediadrop.model.media.Media.slug` :returns: Redirect to :meth:`view` page for media. """ def result(success, message=None, comment=None): if request.is_xhr: result = dict(success=success, message=message) if comment: result['comment'] = render('comments/_list.html', {'comment_to_render': comment}, method='xhtml') return result elif success: return redirect(action='view') else: return self.view(slug, name=name, email=email, body=body, **kwargs) if request.settings['comments_engine'] != 'builtin': abort(404) akismet_key = request.settings['akismet_key'] if akismet_key: akismet = Akismet(agent=USER_AGENT) akismet.key = akismet_key akismet.blog_url = request.settings['akismet_url'] or \ url_for('/', qualified=True) akismet.verify_key() data = {'comment_author': name.encode('utf-8'), 'user_ip': request.environ.get('REMOTE_ADDR'), 'user_agent': request.environ.get('HTTP_USER_AGENT', ''), 'referrer': request.environ.get('HTTP_REFERER', 'unknown'), 'HTTP_ACCEPT': request.environ.get('HTTP_ACCEPT')} if akismet.comment_check(body.encode('utf-8'), data): return result(False, _(u'Your comment has been rejected.')) media = fetch_row(Media, slug=slug) request.perm.assert_permission(u'view', media.resource) c = Comment() name = filter_vulgarity(name) c.author = AuthorWithIP(name, email, request.environ['REMOTE_ADDR']) c.subject = 'Re: %s' % media.title c.body = filter_vulgarity(body) require_review = request.settings['req_comment_approval'] if not require_review: c.reviewed = True c.publishable = True media.comments.append(c) DBSession.flush() send_comment_notification(media, c) if require_review: message = _('Thank you for your comment! We will post it just as ' 'soon as a moderator approves it.') return result(True, message=message) else: return result(True, comment=c)
NotificationsForm, PopularityForm, SiteMapsForm, UploadForm) from mediadrop.lib.base import BaseSettingsController from mediadrop.lib.decorators import autocommit, expose, observable, validate from mediadrop.lib.helpers import filter_vulgarity, redirect, url_for from mediadrop.lib.i18n import LanguageError, Translator from mediadrop.model import Comment, Media from mediadrop.model.meta import DBSession from mediadrop.plugin import events from mediadrop.websetup import appearance_settings, generate_appearance_css import logging log = logging.getLogger(__name__) notifications_form = NotificationsForm( action=url_for(controller='/admin/settings', action='notifications_save')) comments_form = CommentsForm( action=url_for(controller='/admin/settings', action='comments_save')) api_form = APIForm( action=url_for(controller='/admin/settings', action='save_api')) popularity_form = PopularityForm( action=url_for(controller='/admin/settings', action='popularity_save')) upload_form = UploadForm( action=url_for(controller='/admin/settings', action='upload_save')) analytics_form = AnalyticsForm( action=url_for(controller='/admin/settings', action='analytics_save'))
from mediadrop.forms.uploader import UploadForm from mediadrop.lib import email from mediadrop.lib.base import BaseController from mediadrop.lib.decorators import autocommit, expose, observable, validate from mediadrop.lib.helpers import redirect, url_for from mediadrop.lib.storage import add_new_media_file from mediadrop.lib.thumbnails import create_default_thumbs_for, has_thumbs from mediadrop.model import Author, DBSession, get_available_slug, Media from mediadrop.plugin import events import logging log = logging.getLogger(__name__) upload_form = UploadForm( action = url_for(controller='/upload', action='submit'), async_action = url_for(controller='/upload', action='submit_async') ) class UploadController(BaseController): """ Media Upload Controller """ def __before__(self, *args, **kwargs): if not request.settings['appearance_enable_user_uploads']: abort(404) result = BaseController.__before__(self, *args, **kwargs) # BareBonesController will set request.perm if not request.perm.contains_permission('upload'): abort(404)
class MediaController(BaseController): allow_only = has_permission('edit') @expose_xhr('admin/media/index.html', 'admin/media/index-table.html') @paginate('media', items_per_page=15) @observable(events.Admin.MediaController.index) def index(self, page=1, search=None, filter=None, podcast=None, category=None, tag=None, **kwargs): """List media with pagination and filtering. :param page: Page number, defaults to 1. :type page: int :param search: Optional search term to filter by :type search: unicode or None :param podcast_filter: Optional podcast to filter by :type podcast_filter: int or None :rtype: dict :returns: media The list of :class:`~mediadrop.model.media.Media` instances for this page. search The given search term, if any search_form The :class:`~mediadrop.forms.admin.SearchForm` instance podcast The podcast object for rendering if filtering by podcast. """ media = Media.query.options(orm.undefer('comment_count_published')) if search: media = media.admin_search(search) else: media = media.order_by_status()\ .order_by(Media.publish_on.desc(), Media.modified_on.desc()) if not filter: pass elif filter == 'unreviewed': media = media.reviewed(False) elif filter == 'unencoded': media = media.reviewed().encoded(False) elif filter == 'drafts': media = media.drafts() elif filter == 'published': media = media.published() if category: category = fetch_row(Category, slug=category) media = media.filter(Media.categories.contains(category)) if tag: tag = fetch_row(Tag, slug=tag) media = media.filter(Media.tags.contains(tag)) if podcast: podcast = fetch_row(Podcast, slug=podcast) media = media.filter(Media.podcast == podcast) return dict( media=media, search=search, search_form=search_form, media_filter=filter, category=category, tag=tag, podcast=podcast, ) def json_error(self, *args, **kwargs): validation_exception = tmpl_context._current_obj().validation_exception return dict(success=False, message=validation_exception.msg) @expose('admin/media/edit.html') @validate(validators={'podcast': validators.Int()}) @autocommit @observable(events.Admin.MediaController.edit) def edit(self, id, **kwargs): """Display the media forms for editing or adding. This page serves as the error_handler for every kind of edit action, if anything goes wrong with them they'll be redirected here. :param id: Media ID :type id: ``int`` or ``"new"`` :param \*\*kwargs: Extra args populate the form for ``"new"`` media :returns: media :class:`~mediadrop.model.media.Media` instance media_form The :class:`~mediadrop.forms.admin.media.MediaForm` instance media_action ``str`` form submit url media_values ``dict`` form values file_add_form The :class:`~mediadrop.forms.admin.media.AddFileForm` instance file_add_action ``str`` form submit url file_edit_form The :class:`~mediadrop.forms.admin.media.EditFileForm` instance file_edit_action ``str`` form submit url thumb_form The :class:`~mediadrop.forms.admin.ThumbForm` instance thumb_action ``str`` form submit url update_status_form The :class:`~mediadrop.forms.admin.media.UpdateStatusForm` instance update_status_action ``str`` form submit url """ media = fetch_row(Media, id) if tmpl_context.action == 'save' or id == 'new': # Use the values from error_handler or GET for new podcast media media_values = kwargs user = request.perm.user media_values.setdefault('author_name', user.display_name) media_values.setdefault('author_email', user.email_address) else: # Pull the defaults from the media item media_values = dict( podcast=media.podcast_id, slug=media.slug, title=media.title, author_name=media.author.name, author_email=media.author.email, description=media.description, tags=', '.join((tag.name for tag in media.tags)), categories=[category.id for category in media.categories], notes=media.notes, ) # Re-verify the state of our Media object in case the data is nonsensical if id != 'new': media.update_status() return dict( media=media, media_form=media_form, media_action=url_for(action='save'), media_values=media_values, category_tree=Category.query.order_by( Category.name).populated_tree(), file_add_form=add_file_form, file_add_action=url_for(action='add_file'), file_edit_form=edit_file_form, file_edit_action=url_for(action='edit_file'), thumb_form=thumb_form, thumb_action=url_for(action='save_thumb'), update_status_form=update_status_form, update_status_action=url_for(action='update_status'), ) @expose_xhr(request_method='POST') @validate_xhr(media_form, error_handler=edit) @autocommit @observable(events.Admin.MediaController.save) def save(self, id, slug, title, author_name, author_email, description, notes, podcast, tags, categories, delete=None, **kwargs): """Save changes or create a new :class:`~mediadrop.model.media.Media` instance. Form handler the :meth:`edit` action and the :class:`~mediadrop.forms.admin.media.MediaForm`. Redirects back to :meth:`edit` after successful editing and :meth:`index` after successful deletion. """ media = fetch_row(Media, id) if delete: self._delete_media(media) redirect(action='index', id=None) if not slug: slug = slugify(title) elif slug.startswith('_stub_'): slug = slug[len('_stub_'):] if slug != media.slug: media.slug = get_available_slug(Media, slug, media) media.title = title media.author = Author(author_name, author_email) media.description = description media.notes = notes media.podcast_id = podcast media.set_tags(tags) media.set_categories(categories) media.update_status() DBSession.add(media) DBSession.flush() if id == 'new' and not has_thumbs(media): create_default_thumbs_for(media) if request.is_xhr: status_form_xhtml = unicode( update_status_form.display(action=url_for( action='update_status', id=media.id), media=media)) return dict( media_id=media.id, values={'slug': slug}, link=url_for(action='edit', id=media.id), status_form=status_form_xhtml, ) else: redirect(action='edit', id=media.id) @expose('json', request_method='POST') @validate(add_file_form, error_handler=json_error) @autocommit @observable(events.Admin.MediaController.add_file) def add_file(self, id, file=None, url=None, **kwargs): """Save action for the :class:`~mediadrop.forms.admin.media.AddFileForm`. Creates a new :class:`~mediadrop.model.media.MediaFile` from the uploaded file or the local or remote URL. :param id: Media ID. If ``"new"`` a new Media stub is created. :type id: :class:`int` or ``"new"`` :param file: The uploaded file :type file: :class:`cgi.FieldStorage` or ``None`` :param url: A URL to a recognizable audio or video file :type url: :class:`unicode` or ``None`` :rtype: JSON dict :returns: success bool message Error message, if unsuccessful media_id The :attr:`~mediadrop.model.media.Media.id` which is important if new media has just been created. file_id The :attr:`~mediadrop.model.media.MediaFile.id` for the newly created file. edit_form The rendered XHTML :class:`~mediadrop.forms.admin.media.EditFileForm` for this file. status_form The rendered XHTML :class:`~mediadrop.forms.admin.media.UpdateStatusForm` """ if id == 'new': media = Media() user = request.perm.user media.author = Author(user.display_name, user.email_address) # Create a temp stub until we can set it to something meaningful timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') media.title = u'Temporary stub %s' % timestamp media.slug = get_available_slug(Media, '_stub_' + timestamp) media.reviewed = True DBSession.add(media) DBSession.flush() else: media = fetch_row(Media, id) try: media_file = add_new_media_file(media, file, url) except UserStorageError as e: return dict(success=False, message=e.message) if media.slug.startswith('_stub_'): media.title = media_file.display_name media.slug = get_available_slug(Media, '_stub_' + media.title) # The thumbs may have been created already by add_new_media_file if id == 'new' and not has_thumbs(media): create_default_thumbs_for(media) media.update_status() # Render some widgets so the XHTML can be injected into the page edit_form_xhtml = unicode( edit_file_form.display(action=url_for(action='edit_file', id=media.id), file=media_file)) status_form_xhtml = unicode( update_status_form.display(action=url_for(action='update_status', id=media.id), media=media)) data = dict( success=True, media_id=media.id, file_id=media_file.id, file_type=media_file.type, edit_form=edit_form_xhtml, status_form=status_form_xhtml, title=media.title, slug=media.slug, description=media.description, link=url_for(action='edit', id=media.id), duration=helpers.duration_from_seconds(media.duration), ) return data @expose('json', request_method='POST') @autocommit @observable(events.Admin.MediaController.edit_file) def edit_file(self, id, file_id, file_type=None, duration=None, delete=None, bitrate=None, width_height=None, **kwargs): """Save action for the :class:`~mediadrop.forms.admin.media.EditFileForm`. Changes or deletes a :class:`~mediadrop.model.media.MediaFile`. XXX: We do NOT use the @validate decorator due to complications with partial validation. The JS sends only the value it wishes to change, so we only want to validate that one value. FancyValidator.if_missing seems to eat empty values and assign them None, but there's an important difference to us between None (no value from the user) and an empty value (the user is clearing the value of a field). :param id: Media ID :type id: :class:`int` :rtype: JSON dict :returns: success bool message Error message, if unsuccessful status_form Rendered XHTML for the status form, updated to reflect the changes made. """ media = fetch_row(Media, id) data = dict(success=False) file_id = int(file_id) # Just in case validation failed somewhere. for file in media.files: if file.id == file_id: break else: file = None fields = edit_file_form.c try: if file is None: data['message'] = _('File "%s" does not exist.') % file_id elif file_type: file.type = fields.file_type.validate(file_type) data['success'] = True elif duration is not None: media.duration = fields.duration.validate(duration) data['success'] = True data['duration'] = helpers.duration_from_seconds( media.duration) elif width_height is not None: width_height = fields.width_height.validate(width_height) file.width, file.height = width_height or (0, 0) data['success'] = True elif bitrate is not None: file.bitrate = fields.bitrate.validate(bitrate) data['success'] = True elif delete: file.storage.delete(file.unique_id) DBSession.delete(file) DBSession.flush() # media.files must be updated to reflect the file deletion above DBSession.refresh(media) data['success'] = True else: data['message'] = _('No action to perform.') except Invalid, e: data['success'] = False data['message'] = unicode(e) if data['success']: data['file_type'] = file.type media.update_status() DBSession.flush() # Return the rendered widget for injection status_form_xhtml = unicode( update_status_form.display( action=url_for(action='update_status'), media=media)) data['status_form'] = status_form_xhtml return data
from mediadrop.forms.uploader import UploadForm from mediadrop.lib import email from mediadrop.lib.base import BaseController from mediadrop.lib.decorators import autocommit, expose, observable, validate from mediadrop.lib.helpers import redirect, url_for from mediadrop.lib.storage import add_new_media_file from mediadrop.lib.thumbnails import create_default_thumbs_for, has_thumbs from mediadrop.model import Author, DBSession, get_available_slug, Media from mediadrop.plugin import events import logging log = logging.getLogger(__name__) upload_form = UploadForm( action = url_for(controller='/upload', action='submit'), async_action = url_for(controller='/upload', action='submit_async') ) tfile = dict() class UploadController(BaseController): """ Media Upload Controller """ def __before__(self, *args, **kwargs): if not request.settings['appearance_enable_user_uploads']: abort(404) result = BaseController.__before__(self, *args, **kwargs) # BareBonesController will set request.perm
def merge_stubs(self, orig_id, input_id, **kwargs): """Merge in a newly created media item. This is merges media that has just been created. It must have: 1. a non-default thumbnail, or 2. a file, or 3. a title, description, etc :param orig_id: Media ID to copy data to :type orig_id: ``int`` :param input_id: Media ID to source files, thumbs, etc from :type input_id: ``int`` :returns: JSON dict """ orig = fetch_row(Media, orig_id) input = fetch_row(Media, input_id) merged_files = [] # Merge in the file(s) from the input stub if input.slug.startswith('_stub_') and input.files: for file in input.files[:]: # XXX: The filename will still use the old ID file.media = orig merged_files.append(file) DBSession.delete(input) # The original is a file or thumb stub, copy in the new values elif orig.slug.startswith('_stub_') \ and not input.slug.startswith('_stub_'): DBSession.delete(input) DBSession.flush() orig.podcast = input.podcast orig.title = input.title orig.subtitle = input.subtitle orig.slug = input.slug orig.author = input.author orig.description = input.description orig.notes = input.notes orig.duration = input.duration orig.views = input.views orig.likes = input.likes orig.publish_on = input.publish_on orig.publish_until = input.publish_until orig.categories = input.categories orig.tags = input.tags orig.update_popularity() # Copy the input thumb over the default thumbnail elif input.slug.startswith('_stub_') \ and has_default_thumbs(orig) \ and not has_default_thumbs(input): for key, dst_path in thumb_paths(orig).iteritems(): src_path = thumb_path(input, key) # This will raise an OSError on Windows, but not *nix os.rename(src_path, dst_path) DBSession.delete(input) # Report an error else: return dict( success = False, message = u'No merge operation fits.', ) orig.update_status() status_form_xhtml = unicode(update_status_form.display( action=url_for(action='update_status', id=orig.id), media=orig)) file_xhtml = {} for file in merged_files: file_xhtml[file.id] = unicode(edit_file_form.display( action=url_for(action='edit_file', id=orig.id), file=file)) return dict( success = True, media_id = orig.id, title = orig.title, link = url_for(action='edit', id=orig.id), status_form = status_form_xhtml, file_forms = file_xhtml, )
def save(self, id, slug, title, author_name, author_email, description, notes, podcast, tags, categories, delete=None, **kwargs): """Save changes or create a new :class:`~mediadrop.model.media.Media` instance. Form handler the :meth:`edit` action and the :class:`~mediadrop.forms.admin.media.MediaForm`. Redirects back to :meth:`edit` after successful editing and :meth:`index` after successful deletion. """ media = fetch_row(Media, id) if delete: self._delete_media(media) redirect(action='index', id=None) if not slug: slug = slugify(title) elif slug.startswith('_stub_'): slug = slug[len('_stub_'):] if slug != media.slug: media.slug = get_available_slug(Media, slug, media) media.title = title media.author = Author(author_name, author_email) media.description = description media.notes = notes media.podcast_id = podcast media.set_tags(tags) media.set_categories(categories) media.update_status() DBSession.add(media) DBSession.flush() if id == 'new' and not has_thumbs(media): create_default_thumbs_for(media) if request.is_xhr: status_form_xhtml = unicode( update_status_form.display(action=url_for( action='update_status', id=media.id), media=media)) return dict( media_id=media.id, values={'slug': slug}, link=url_for(action='edit', id=media.id), status_form=status_form_xhtml, ) else: redirect(action='edit', id=media.id)
from mediadrop.lib.templating import render from mediadrop.lib.thumbnails import thumb_path, thumb_paths, create_thumbs_for, create_default_thumbs_for, has_thumbs, has_default_thumbs, delete_thumbs from mediadrop.model import (Author, Category, Media, Podcast, Tag, fetch_row, get_available_slug, slugify) from mediadrop.model.meta import DBSession from mediadrop.plugin import events import logging log = logging.getLogger(__name__) media_form = MediaForm() add_file_form = AddFileForm() edit_file_form = EditFileForm() thumb_form = ThumbForm() update_status_form = UpdateStatusForm() search_form = SearchForm(action=url_for(controller='/admin/media', action='index')) class MediaController(BaseController): allow_only = has_permission('edit') @expose_xhr('admin/media/index.html', 'admin/media/index-table.html') @paginate('media', items_per_page=15) @observable(events.Admin.MediaController.index) def index(self, page=1, search=None, filter=None, podcast=None, category=None, tag=None, **kwargs): """List media with pagination and filtering. :param page: Page number, defaults to 1. :type page: int :param search: Optional search term to filter by :type search: unicode or None
def comment(self, slug, name='', email=None, body='', **kwargs): """Post a comment from :class:`~mediadrop.forms.comments.PostCommentForm`. :param slug: The media :attr:`~mediadrop.model.media.Media.slug` :returns: Redirect to :meth:`view` page for media. """ def result(success, message=None, comment=None): if request.is_xhr: result = dict(success=success, message=message) if comment: result['comment'] = render('comments/_list.html', {'comment_to_render': comment}, method='xhtml') return result elif success: return redirect(action='view') else: return self.view(slug, name=name, email=email, body=body, **kwargs) if request.settings['comments_engine'] != 'builtin': abort(404) akismet_key = request.settings['akismet_key'] if akismet_key: akismet = Akismet(agent=USER_AGENT) akismet.key = akismet_key akismet.blog_url = request.settings['akismet_url'] or \ url_for('/', qualified=True) akismet.verify_key() data = { 'comment_author': name.encode('utf-8'), 'user_ip': request.environ.get('REMOTE_ADDR'), 'user_agent': request.environ.get('HTTP_USER_AGENT', ''), 'referrer': request.environ.get('HTTP_REFERER', 'unknown'), 'HTTP_ACCEPT': request.environ.get('HTTP_ACCEPT') } if akismet.comment_check(body.encode('utf-8'), data): return result(False, _(u'Your comment has been rejected.')) media = fetch_row(Media, slug=slug) request.perm.assert_permission(u'view', media.resource) c = Comment() aux = DBSession.query(User).filter(User.display_name == name) require_review = request.settings['req_comment_approval'] if not require_review: c.reviewed = True c.publishable = True if request.perm.user.display_name != "Anonymous User": name = request.perm.user.display_name email = request.perm.user.email_address if aux: name = filter_vulgarity(name) c.author = AuthorWithIP(name, email, request.environ['REMOTE_ADDR']) c.subject = 'Re: %s' % media.title c.body = filter_vulgarity(body) media.comments.append(c) DBSession.flush() send_comment_notification(media, c) if require_review: message = _('Thank you for your comment! We will post it just as ' 'soon as a moderator approves it.') return result(True, message=message) else: return result(True, comment=c)
from mediadrop.lib.thumbnails import thumb_path, thumb_paths, create_thumbs_for, create_default_thumbs_for, has_thumbs, has_default_thumbs, delete_thumbs from mediadrop.model import (Author, Category, Media, Podcast, Tag, fetch_row, get_available_slug, slugify) from mediadrop.model.meta import DBSession from mediadrop.plugin import events import logging log = logging.getLogger(__name__) media_form = MediaForm() add_file_form = AddFileForm() edit_file_form = EditFileForm() thumb_form = ThumbForm() update_status_form = UpdateStatusForm() search_form = SearchForm( action=url_for(controller='/admin/media', action='index')) class MediaController(BaseController): allow_only = has_permission('edit') @expose_xhr('admin/media/index.html', 'admin/media/index-table.html') @paginate('media', items_per_page=15) @observable(events.Admin.MediaController.index) def index(self, page=1, search=None, filter=None, podcast=None, category=None, tag=None,
def edit(self, id, **kwargs): """Display the media forms for editing or adding. This page serves as the error_handler for every kind of edit action, if anything goes wrong with them they'll be redirected here. :param id: Media ID :type id: ``int`` or ``"new"`` :param \*\*kwargs: Extra args populate the form for ``"new"`` media :returns: media :class:`~mediadrop.model.media.Media` instance media_form The :class:`~mediadrop.forms.admin.media.MediaForm` instance media_action ``str`` form submit url media_values ``dict`` form values file_add_form The :class:`~mediadrop.forms.admin.media.AddFileForm` instance file_add_action ``str`` form submit url file_edit_form The :class:`~mediadrop.forms.admin.media.EditFileForm` instance file_edit_action ``str`` form submit url thumb_form The :class:`~mediadrop.forms.admin.ThumbForm` instance thumb_action ``str`` form submit url update_status_form The :class:`~mediadrop.forms.admin.media.UpdateStatusForm` instance update_status_action ``str`` form submit url """ media = fetch_row(Media, id) if tmpl_context.action == 'save' or id == 'new': # Use the values from error_handler or GET for new podcast media media_values = kwargs user = request.perm.user media_values.setdefault('author_name', user.display_name) media_values.setdefault('author_email', user.email_address) else: # Pull the defaults from the media item media_values = dict( podcast=media.podcast_id, slug=media.slug, title=media.title, author_name=media.author.name, author_email=media.author.email, description=media.description, tags=', '.join((tag.name for tag in media.tags)), categories=[category.id for category in media.categories], notes=media.notes, ) # Re-verify the state of our Media object in case the data is nonsensical if id != 'new': media.update_status() return dict( media=media, media_form=media_form, media_action=url_for(action='save'), media_values=media_values, category_tree=Category.query.order_by( Category.name).populated_tree(), file_add_form=add_file_form, file_add_action=url_for(action='add_file'), file_edit_form=edit_file_form, file_edit_action=url_for(action='edit_file'), thumb_form=thumb_form, thumb_action=url_for(action='save_thumb'), update_status_form=update_status_form, update_status_action=url_for(action='update_status'), )
APIForm, AnalyticsForm, CommentsForm, GeneralForm, GoogleAPIForm, NotificationsForm, PopularityForm, SiteMapsForm, UploadForm) from mediadrop.lib.base import BaseSettingsController from mediadrop.lib.decorators import autocommit, expose, observable, validate from mediadrop.lib.helpers import filter_vulgarity, redirect, url_for from mediadrop.lib.i18n import LanguageError, Translator from mediadrop.model import Comment, Media from mediadrop.model.meta import DBSession from mediadrop.plugin import events from mediadrop.websetup import appearance_settings, generate_appearance_css import logging log = logging.getLogger(__name__) notifications_form = NotificationsForm( action=url_for(controller='/admin/settings', action='notifications_save')) comments_form = CommentsForm( action=url_for(controller='/admin/settings', action='comments_save')) googleapi_form = GoogleAPIForm( action=url_for(controller='/admin/settings', action='googleapi_save')) api_form = APIForm( action=url_for(controller='/admin/settings', action='save_api')) popularity_form = PopularityForm( action=url_for(controller='/admin/settings', action='popularity_save')) upload_form = UploadForm( action=url_for(controller='/admin/settings', action='upload_save'))
def edit(self, id, **kwargs): """Display the podcast forms for editing or adding. This page serves as the error_handler for every kind of edit action, if anything goes wrong with them they'll be redirected here. :param id: Podcast ID :type id: ``int`` or ``"new"`` :param \*\*kwargs: Extra args populate the form for ``"new"`` podcasts :returns: podcast :class:`~mediadrop.model.podcasts.Podcast` instance form :class:`~mediadrop.forms.admin.podcasts.PodcastForm` instance form_action ``str`` form submit url form_values ``dict`` form values thumb_form :class:`~mediadrop.forms.admin.ThumbForm` instance thumb_action ``str`` form submit url """ podcast = fetch_row(Podcast, id) if tmpl_context.action == 'save' or id == 'new': form_values = kwargs user = request.perm.user form_values.setdefault('author_name', user.display_name) form_values.setdefault('author_email', user.email_address) form_values.setdefault('feed', {}).setdefault( 'feed_url', _('Save the podcast to get your feed URL')) else: explicit_values = {True: 'yes', False: 'clean', None: 'no'} form_values = dict( slug=podcast.slug, title=podcast.title, subtitle=podcast.subtitle, author_name=podcast.author and podcast.author.name or None, author_email=podcast.author and podcast.author.email or None, description=podcast.description, details=dict( explicit=explicit_values.get(podcast.explicit), category=podcast.category, copyright=podcast.copyright, ), feed=dict( feed_url=url_for(controller='/podcasts', action='feed', slug=podcast.slug, qualified=True), itunes_url=podcast.itunes_url, feedburner_url=podcast.feedburner_url, ), ) return dict( podcast=podcast, form=podcast_form, form_action=url_for(action='save'), form_values=form_values, thumb_form=thumb_form, thumb_action=url_for(action='save_thumb'), )
def save(self, id, delete=None, **kwargs): """Save changes or create a category. See :class:`~mediadrop.forms.admin.settings.categories.CategoryForm` for POST vars. :param id: Category ID :param delete: If true the category is to be deleted rather than saved. :type delete: bool :rtype: JSON dict :returns: success bool """ if tmpl_context.form_errors: if request.is_xhr: return dict(success=False, errors=tmpl_context.form_errors) else: # TODO: Add error reporting for users with JS disabled? return redirect(action='edit') cat = fetch_row(Category, id) if delete: DBSession.delete(cat) data = dict( success=True, id=cat.id, parent_options=unicode(category_form.c['parent_id'].display()), ) else: cat.name = kwargs['name'] cat.slug = get_available_slug(Category, kwargs['slug'], cat) if kwargs['parent_id']: parent = fetch_row(Category, kwargs['parent_id']) if parent is not cat and cat not in parent.ancestors(): cat.parent = parent else: cat.parent = None DBSession.add(cat) DBSession.flush() data = dict( success=True, id=cat.id, name=cat.name, slug=cat.slug, parent_id=cat.parent_id, parent_options=unicode(category_form.c['parent_id'].display()), depth=cat.depth(), row=unicode( category_row_form.display( action=url_for(id=cat.id), category=cat, depth=cat.depth(), first_child=True, )), ) if request.is_xhr: return data else: redirect(action='index', id=None)
def edit(self, id, **kwargs): """Display the podcast forms for editing or adding. This page serves as the error_handler for every kind of edit action, if anything goes wrong with them they'll be redirected here. :param id: Podcast ID :type id: ``int`` or ``"new"`` :param \*\*kwargs: Extra args populate the form for ``"new"`` podcasts :returns: podcast :class:`~mediadrop.model.podcasts.Podcast` instance form :class:`~mediadrop.forms.admin.podcasts.PodcastForm` instance form_action ``str`` form submit url form_values ``dict`` form values thumb_form :class:`~mediadrop.forms.admin.ThumbForm` instance thumb_action ``str`` form submit url """ podcast = fetch_row(Podcast, id) if tmpl_context.action == 'save' or id == 'new': form_values = kwargs user = request.perm.user form_values.setdefault('author_name', user.display_name) form_values.setdefault('author_email', user.email_address) form_values.setdefault('feed', {}).setdefault('feed_url', _('Save the podcast to get your feed URL')) else: explicit_values = {True: 'yes', False: 'clean', None: 'no'} form_values = dict( slug = podcast.slug, title = podcast.title, subtitle = podcast.subtitle, author_name = podcast.author and podcast.author.name or None, author_email = podcast.author and podcast.author.email or None, description = podcast.description, details = dict( explicit = explicit_values.get(podcast.explicit), category = podcast.category, copyright = podcast.copyright, ), feed = dict( feed_url = url_for(controller='/podcasts', action='feed', slug=podcast.slug, qualified=True), itunes_url = podcast.itunes_url, feedburner_url = podcast.feedburner_url, ), ) return dict( podcast = podcast, form = podcast_form, form_action = url_for(action='save'), form_values = form_values, thumb_form = thumb_form, thumb_action = url_for(action='save_thumb'), )
from mediadrop.forms.uploader import UploadForm from mediadrop.lib import email from mediadrop.lib.base import BaseController from mediadrop.lib.decorators import autocommit, expose, observable, validate from mediadrop.lib.helpers import redirect, url_for from mediadrop.lib.storage import add_new_media_file from mediadrop.lib.thumbnails import create_default_thumbs_for, has_thumbs from mediadrop.model import Author, DBSession, get_available_slug, Media from mediadrop.plugin import events import logging log = logging.getLogger(__name__) upload_form = UploadForm( action=url_for(controller="/upload", action="submit"), async_action=url_for(controller="/upload", action="submit_async"), ) class UploadController(BaseController): """ Media Upload Controller """ def __before__(self, *args, **kwargs): if not request.settings["appearance_enable_user_uploads"]: abort(404) result = BaseController.__before__(self, *args, **kwargs) # BareBonesController will set request.perm if not request.perm.contains_permission("upload"):
def edit(self, id, **kwargs): """Display the media forms for editing or adding. This page serves as the error_handler for every kind of edit action, if anything goes wrong with them they'll be redirected here. :param id: Media ID :type id: ``int`` or ``"new"`` :param \*\*kwargs: Extra args populate the form for ``"new"`` media :returns: media :class:`~mediadrop.model.media.Media` instance media_form The :class:`~mediadrop.forms.admin.media.MediaForm` instance media_action ``str`` form submit url media_values ``dict`` form values file_add_form The :class:`~mediadrop.forms.admin.media.AddFileForm` instance file_add_action ``str`` form submit url file_edit_form The :class:`~mediadrop.forms.admin.media.EditFileForm` instance file_edit_action ``str`` form submit url thumb_form The :class:`~mediadrop.forms.admin.ThumbForm` instance thumb_action ``str`` form submit url update_status_form The :class:`~mediadrop.forms.admin.media.UpdateStatusForm` instance update_status_action ``str`` form submit url """ media = fetch_row(Media, id) if tmpl_context.action == 'save' or id == 'new': # Use the values from error_handler or GET for new podcast media media_values = kwargs user = request.perm.user media_values.setdefault('author_name', user.display_name) media_values.setdefault('author_email', user.email_address) else: # Pull the defaults from the media item media_values = dict( podcast = media.podcast_id, slug = media.slug, title = media.title, author_name = media.author.name, author_email = media.author.email, description = media.description, tags = ', '.join((tag.name for tag in media.tags)), categories = [category.id for category in media.categories], notes = media.notes, ) # Re-verify the state of our Media object in case the data is nonsensical if id != 'new': media.update_status() return dict( media = media, media_form = media_form, media_action = url_for(action='save'), media_values = media_values, category_tree = Category.query.order_by(Category.name).populated_tree(), file_add_form = add_file_form, file_add_action = url_for(action='add_file'), file_edit_form = edit_file_form, file_edit_action = url_for(action='edit_file'), thumb_form = thumb_form, thumb_action = url_for(action='save_thumb'), update_status_form = update_status_form, update_status_action = url_for(action='update_status'), )
from mediadrop.forms.admin.comments import EditCommentForm from mediadrop.lib.auth import has_permission from mediadrop.lib.base import BaseController from mediadrop.lib.decorators import (autocommit, expose, expose_xhr, observable, paginate) from mediadrop.lib.helpers import redirect, url_for from mediadrop.model import Comment, Media, fetch_row from mediadrop.model.meta import DBSession from mediadrop.plugin import events import logging log = logging.getLogger(__name__) edit_form = EditCommentForm() search_form = SearchForm( action=url_for(controller='/admin/comments', action='index')) class CommentsController(BaseController): allow_only = has_permission('edit') @expose_xhr('admin/comments/index.html', 'admin/comments/index-table.html') @paginate('comments', items_per_page=25) @observable(events.Admin.CommentsController.index) def index(self, page=1, search=None, media_filter=None, **kwargs): """List comments with pagination and filtering. :param page: Page number, defaults to 1. :type page: int :param search: Optional search term to filter by :type search: unicode or None
def add_file(self, id, file=None, url=None, **kwargs): """Save action for the :class:`~mediadrop.forms.admin.media.AddFileForm`. Creates a new :class:`~mediadrop.model.media.MediaFile` from the uploaded file or the local or remote URL. :param id: Media ID. If ``"new"`` a new Media stub is created. :type id: :class:`int` or ``"new"`` :param file: The uploaded file :type file: :class:`cgi.FieldStorage` or ``None`` :param url: A URL to a recognizable audio or video file :type url: :class:`unicode` or ``None`` :rtype: JSON dict :returns: success bool message Error message, if unsuccessful media_id The :attr:`~mediadrop.model.media.Media.id` which is important if new media has just been created. file_id The :attr:`~mediadrop.model.media.MediaFile.id` for the newly created file. edit_form The rendered XHTML :class:`~mediadrop.forms.admin.media.EditFileForm` for this file. status_form The rendered XHTML :class:`~mediadrop.forms.admin.media.UpdateStatusForm` """ if id == 'new': media = Media() user = request.perm.user media.author = Author(user.display_name, user.email_address) # Create a temp stub until we can set it to something meaningful timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') media.title = u'Temporary stub %s' % timestamp media.slug = get_available_slug(Media, '_stub_' + timestamp) media.reviewed = True DBSession.add(media) DBSession.flush() else: media = fetch_row(Media, id) try: media_file = add_new_media_file(media, file, url) except UserStorageError as e: return dict(success=False, message=e.message) if media.slug.startswith('_stub_'): media.title = media_file.display_name media.slug = get_available_slug(Media, '_stub_' + media.title) # The thumbs may have been created already by add_new_media_file if id == 'new' and not has_thumbs(media): create_default_thumbs_for(media) media.update_status() # Render some widgets so the XHTML can be injected into the page edit_form_xhtml = unicode(edit_file_form.display( action=url_for(action='edit_file', id=media.id), file=media_file)) status_form_xhtml = unicode(update_status_form.display( action=url_for(action='update_status', id=media.id), media=media)) data = dict( success = True, media_id = media.id, file_id = media_file.id, file_type = media_file.type, edit_form = edit_form_xhtml, status_form = status_form_xhtml, title = media.title, slug = media.slug, description = media.description, link = url_for(action='edit', id=media.id), duration = helpers.duration_from_seconds(media.duration), ) return data
def _info(self, media, podcast_slugs=None, include_embed=False): """ Return a **media_info** dict--a JSON-ready dict for describing a media instance. :rtype: JSON-ready dict :returns: The returned dict has the following fields: author (unicode) The name of the :attr:`author <mediadrop.model.media.Media.author>` of the media instance. categories (dict of unicode) A JSON-ready dict representing the categories the media instance is in. Keys are the unique :attr:`slugs <mediadrop.model.podcasts.Podcast.slug>` for each category, values are the human-readable :attr:`title <mediadrop.model.podcasts.podcast.Title>` of that category. id (int) The numeric unique :attr:`id <mediadrop.model.media.Media.id>` of the media instance. slug (unicode) The more human readable unique identifier (:attr:`slug <mediadrop.model.media.Media.slug>`) of the media instance. url (unicode) A permalink (HTTP) to the MediaDrop view page for the media instance. embed (unicode) HTML code that can be used to embed the video in another site. title (unicode) The :attr:`title <mediadrop.model.media.Media.title>` of the media instance. type (string, one of ['%s', '%s']) The :attr:`type <mediadrop.model.media.Media.type>` of the media instance podcast (unicode or None) The :attr:`slug <mediadrop.model.podcasts.Podcast.slug>` of the :class:`podcast <mediadrop.model.podcasts.Podcast>` that the media instance has been published under, or None description (unicode) An XHTML :attr:`description <mediadrop.model.media.Media.description>` of the media instance. description_plain (unicode) A plain text :attr:`description <mediadrop.model.media.Media.description_plain>` of the media instance. comment_count (int) The number of published comments on the media instance. publish_on (unicode) The date of publishing in "YYYY-MM-DD HH:MM:SS" (ISO 8601) format. e.g. "2010-02-16 15:06:49" likes (int) The number of :attr:`like votes <mediadrop.model.media.Media.likes>` that the media instance has received. views (int) The number of :attr:`views <mediadrop.model.media.Media.views>` that the media instance has received. thumbs (dict) A dict of dicts containing URLs, width and height of different sizes of thumbnails. The default sizes are 's', 'm' and 'l'. Using medium for example:: medium_url = thumbs['m']['url'] medium_width = thumbs['m']['x'] medium_height = thumbs['m']['y'] """ if media.podcast_id: media_url = url_for(controller='/media', action='view', slug=media.slug, podcast_slug=media.podcast.slug, qualified=True) else: media_url = url_for_media(media, qualified=True) if media.podcast_id is None: podcast_slug = None elif podcast_slugs: podcast_slug = podcast_slugs[media.podcast_id] else: podcast_slug = DBSession.query(Podcast.slug)\ .filter_by(id=media.podcast_id).scalar() thumbs = {} for size in config['thumb_sizes'][media._thumb_dir].iterkeys(): thumbs[size] = thumb(media, size, qualified=True) info = dict( id = media.id, slug = media.slug, url = media_url, title = media.title, author = media.author.name, type = media.type, podcast = podcast_slug, description = media.description, description_plain = media.description_plain, comment_count = media.comment_count_published, publish_on = unicode(media.publish_on), likes = media.likes, views = media.views, thumbs = thumbs, categories = dict((c.slug, c.name) for c in list(media.categories)), ) if include_embed: info['embed'] = unicode(helpers.embed_player(media)) return info
from mediadrop.forms.admin import SearchForm from mediadrop.forms.admin.comments import EditCommentForm from mediadrop.lib.auth import has_permission from mediadrop.lib.base import BaseController from mediadrop.lib.decorators import (autocommit, expose, expose_xhr, observable, paginate) from mediadrop.lib.helpers import redirect, url_for from mediadrop.model import Comment, Media, fetch_row from mediadrop.model.meta import DBSession from mediadrop.plugin import events import logging log = logging.getLogger(__name__) edit_form = EditCommentForm() search_form = SearchForm(action=url_for(controller='/admin/comments', action='index')) class CommentsController(BaseController): allow_only = has_permission('edit') @expose_xhr('admin/comments/index.html', 'admin/comments/index-table.html') @paginate('comments', items_per_page=25) @observable(events.Admin.CommentsController.index) def index(self, page=1, search=None, media_filter=None, **kwargs): """List comments with pagination and filtering. :param page: Page number, defaults to 1. :type page: int :param search: Optional search term to filter by :type search: unicode or None
def view(self, slug, podcast_slug=None, **kwargs): """Display the media player, info and comments. :param slug: The :attr:`~mediadrop.models.media.Media.slug` to lookup :param podcast_slug: The :attr:`~mediadrop.models.podcasts.Podcast.slug` for podcast this media belongs to. Although not necessary for looking up the media, it tells us that the podcast slug was specified in the URL and therefore we reached this action by the preferred route. :rtype dict: :returns: media The :class:`~mediadrop.model.media.Media` instance for display. related_media A list of :class:`~mediadrop.model.media.Media` instances that rank as topically related to the given media item. comments A list of :class:`~mediadrop.model.comments.Comment` instances associated with the selected media item. comment_form_action ``str`` comment form action comment_form_values ``dict`` form values next_episode The next episode in the podcast series, if this media belongs to a podcast, another :class:`~mediadrop.model.media.Media` instance. """ media = fetch_row(Media, slug=slug) request.perm.assert_permission(u'view', media.resource) if media.podcast_id is not None: # Always view podcast media from a URL that shows the context of the podcast if url_for() != url_for(podcast_slug=media.podcast.slug): redirect(podcast_slug=media.podcast.slug) views = Views_Counter.query.filter(Views_Counter.media_id == media.id) #csrf = kwargs['environ']['paste.cookies'][0]['csrftoken'].value #views = views.filter(Views_Counter.csrftoken == csrf) views = views.all() if not views: try: temp = Views_Counter() temp.media_id = media.id temp.csrftoken = csrf print(temp) print(vars(temp)) DBSession.add(temp) DBSession.commit() except: DBSession.rollback() if request.settings['comments_engine'] == 'facebook': response.facebook = Facebook(request.settings['facebook_appid']) related_media = viewable_media(Media.query.related(media))[:6] # TODO: finish implementation of different 'likes' buttons # e.g. the default one, plus a setting to use facebook. return dict( media=media, related_media=related_media, comments=media.comments.published().all(), comment_form_action=url_for(action='comment'), comment_form_values=kwargs, )
def merge_stubs(self, orig_id, input_id, **kwargs): """Merge in a newly created media item. This is merges media that has just been created. It must have: 1. a non-default thumbnail, or 2. a file, or 3. a title, description, etc :param orig_id: Media ID to copy data to :type orig_id: ``int`` :param input_id: Media ID to source files, thumbs, etc from :type input_id: ``int`` :returns: JSON dict """ orig = fetch_row(Media, orig_id) input = fetch_row(Media, input_id) merged_files = [] # Merge in the file(s) from the input stub if input.slug.startswith('_stub_') and input.files: for file in input.files[:]: # XXX: The filename will still use the old ID file.media = orig merged_files.append(file) DBSession.delete(input) # The original is a file or thumb stub, copy in the new values elif orig.slug.startswith('_stub_') \ and not input.slug.startswith('_stub_'): DBSession.delete(input) DBSession.flush() orig.podcast = input.podcast orig.title = input.title orig.subtitle = input.subtitle orig.slug = input.slug orig.author = input.author orig.description = input.description orig.notes = input.notes orig.duration = input.duration orig.views = input.views orig.likes = input.likes orig.publish_on = input.publish_on orig.publish_until = input.publish_until orig.categories = input.categories orig.tags = input.tags orig.update_popularity() # Copy the input thumb over the default thumbnail elif input.slug.startswith('_stub_') \ and has_default_thumbs(orig) \ and not has_default_thumbs(input): for key, dst_path in thumb_paths(orig).iteritems(): src_path = thumb_path(input, key) # This will raise an OSError on Windows, but not *nix os.rename(src_path, dst_path) DBSession.delete(input) # Report an error else: return dict( success=False, message=u'No merge operation fits.', ) orig.update_status() status_form_xhtml = unicode( update_status_form.display(action=url_for(action='update_status', id=orig.id), media=orig)) file_xhtml = {} for file in merged_files: file_xhtml[file.id] = unicode( edit_file_form.display(action=url_for(action='edit_file', id=orig.id), file=file)) return dict( success=True, media_id=orig.id, title=orig.title, link=url_for(action='edit', id=orig.id), status_form=status_form_xhtml, file_forms=file_xhtml, )
def add_file(self, id, file=None, url=None, **kwargs): """Save action for the :class:`~mediadrop.forms.admin.media.AddFileForm`. Creates a new :class:`~mediadrop.model.media.MediaFile` from the uploaded file or the local or remote URL. :param id: Media ID. If ``"new"`` a new Media stub is created. :type id: :class:`int` or ``"new"`` :param file: The uploaded file :type file: :class:`cgi.FieldStorage` or ``None`` :param url: A URL to a recognizable audio or video file :type url: :class:`unicode` or ``None`` :rtype: JSON dict :returns: success bool message Error message, if unsuccessful media_id The :attr:`~mediadrop.model.media.Media.id` which is important if new media has just been created. file_id The :attr:`~mediadrop.model.media.MediaFile.id` for the newly created file. edit_form The rendered XHTML :class:`~mediadrop.forms.admin.media.EditFileForm` for this file. status_form The rendered XHTML :class:`~mediadrop.forms.admin.media.UpdateStatusForm` """ if id == 'new': media = Media() user = request.perm.user media.author = Author(user.display_name, user.email_address) # Create a temp stub until we can set it to something meaningful timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') media.title = u'Temporary stub %s' % timestamp media.slug = get_available_slug(Media, '_stub_' + timestamp) media.reviewed = True DBSession.add(media) DBSession.flush() else: media = fetch_row(Media, id) try: media_file = add_new_media_file(media, file, url) except UserStorageError as e: return dict(success=False, message=e.message) if media.slug.startswith('_stub_'): media.title = media_file.display_name media.slug = get_available_slug(Media, '_stub_' + media.title) # The thumbs may have been created already by add_new_media_file if id == 'new' and not has_thumbs(media): create_default_thumbs_for(media) media.update_status() # Render some widgets so the XHTML can be injected into the page edit_form_xhtml = unicode( edit_file_form.display(action=url_for(action='edit_file', id=media.id), file=media_file)) status_form_xhtml = unicode( update_status_form.display(action=url_for(action='update_status', id=media.id), media=media)) data = dict( success=True, media_id=media.id, file_id=media_file.id, file_type=media_file.type, edit_form=edit_form_xhtml, status_form=status_form_xhtml, title=media.title, slug=media.slug, description=media.description, link=url_for(action='edit', id=media.id), duration=helpers.duration_from_seconds(media.duration), ) return data