def test_metagroup_assignment_does_not_fail_if_groups_are_not_found_in_db(self): DBSession.delete(self.anonymous) DBSession.delete(self.authenticated) DBSession.flush() user = User.example() self.assert_user_groups([], user)
def save(self, id, display_name, group_name, permissions, delete=None, **kwargs): """Save changes or create a new :class:`~mediadrop.model.auth.Group` instance. :param id: Group ID. If ``"new"`` a new group is created. :type id: ``int`` or ``"new"`` :returns: Redirect back to :meth:`index` after successful save. """ group = fetch_row(Group, id) if delete: DBSession.delete(group) redirect(action='index', id=None) group.display_name = display_name group.group_name = group_name if permissions: query = DBSession.query(Permission).filter( Permission.permission_id.in_(permissions)) group.permissions = list(query.all()) else: group.permissions = [] DBSession.add(group) redirect(action='index', id=None)
def save(self, id, email_address, display_name, login_details, delete=None, **kwargs): """Save changes or create a new :class:`~mediadrop.model.auth.User` instance. :param id: User ID. If ``"new"`` a new user is created. :type id: ``int`` or ``"new"`` :returns: Redirect back to :meth:`index` after successful save. """ user = fetch_row(User, id) if delete: all_deletes = dict( comments=Comment.query.filter( Comment.author_name == user.user_name), podcasts=Podcast.query.filter( Podcast.author_name == user.user_name), medias=Media.query.filter(Media.author_name == user.user_name)) for need_delete in all_deletes: for element in all_deletes[need_delete]: DBSession.delete(element) DBSession.delete(user) redirect(action='index', id=None) user.display_name = display_name user.email_address = email_address if login_details['user_name'] != 'anonymous': user.user_name = login_details['user_name'] else: raise webob.exc.HTTPUnauthorized().exception password = login_details['password'] if password is not None and password != '': user.password = password if login_details['groups']: query = DBSession.query(Group).filter( Group.group_id.in_(login_details['groups'])) user.groups = list(query.all()) else: user.groups = [] DBSession.add(user) # Check if we're changing the logged in user's own password if user.id == request.perm.user.id \ and password is not None and password != '': DBSession.commit() # repoze.who sees the Unauthorized response and clears the cookie, # forcing a fresh login with the new password raise webob.exc.HTTPUnauthorized().exception redirect(action='index', id=None)
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 test_metagroup_assignment_does_not_fail_if_groups_are_not_found_in_db( self): DBSession.delete(self.anonymous) DBSession.delete(self.authenticated) DBSession.flush() user = User.example() self.assert_user_groups([], user)
def save_thumb(self, id, thumb=None, **kwargs): """Save a thumbnail uploaded with :class:`~mediadrop.forms.admin.ThumbForm`. :param id: Media ID. If ``"new"`` a new Media stub is created. :type id: ``int`` or ``"new"`` :param file: The uploaded file :type file: :class:`cgi.FieldStorage` or ``None`` :rtype: JSON dict :returns: success bool message Error message, if unsuccessful id The :attr:`~mediadrop.model.media.Media.id` which is important if a new media has just been created. """ user = request.perm.user if id == 'new': media = Media() user = request.perm.user media.author = Author(user.display_name, user.email_address) media.title = os.path.basename(kwargs['file'].filename) media.slug = get_available_slug(Media, '_stub_' + media.title) DBSession.add(media) DBSession.flush() else: media = fetch_row(Media, id) group = request.perm.user.groups[0].group_name if media.author.name != user.display_name and group != 'admins': raise try: # Create JPEG thumbs create_thumbs_for(media, kwargs['file'].file, kwargs['file'].filename) success = True message = None except IOError, e: success = False if id == 'new': DBSession.delete(media) if e.errno == 13: message = _('Permission denied, cannot write file') elif e.message == 'cannot identify image file': message = _('Unsupported image type: %s') \ % os.path.splitext(kwargs['file'].filename)[1].lstrip('.') elif e.message == 'cannot read interlaced PNG files': message = _('Interlaced PNGs are not supported.') else: raise
def save(self, id, slug, title, subtitle, author_name, author_email, description, details, feed, delete=None, **kwargs): """Save changes or create a new :class:`~mediadrop.model.podcasts.Podcast` instance. Form handler the :meth:`edit` action and the :class:`~mediadrop.forms.admin.podcasts.PodcastForm`. Redirects back to :meth:`edit` after successful editing and :meth:`index` after successful deletion. """ podcast = fetch_row(Podcast, id) if delete: DBSession.delete(podcast) DBSession.commit() delete_thumbs(podcast) redirect(action='index', id=None) if not slug: slug = title if slug != podcast.slug: podcast.slug = get_available_slug(Podcast, slug, podcast) podcast.title = title podcast.subtitle = subtitle podcast.author = Author(author_name, author_email) podcast.description = description podcast.copyright = details['copyright'] podcast.category = details['category'] podcast.itunes_url = feed['itunes_url'] podcast.feedburner_url = feed['feedburner_url'] podcast.explicit = { 'yes': True, 'clean': False }.get(details['explicit'], None) if id == 'new': DBSession.add(podcast) DBSession.flush() create_default_thumbs_for(podcast) redirect(action='edit', id=podcast.id)
def delete(self, id, **kwargs): """Delete a group. :param id: Group ID. :type id: ``int`` :returns: Redirect back to :meth:`index` after successful delete. """ group = fetch_row(Group, id) DBSession.delete(group) if request.is_xhr: return dict(success=True) redirect(action='index', id=None)
def delete(self, id, **kwargs): """Delete a user. :param id: User ID. :type id: ``int`` :returns: Redirect back to :meth:`index` after successful delete. """ user = fetch_row(User, id) DBSession.delete(user) if request.is_xhr: return dict(success=True) redirect(action='index', id=None)
def _delete_media(self, media): # FIXME: Ensure that if the first file is deleted from the file system, # then the second fails, the first file is deleted from the # file system and not linking to a nonexistent file. # Delete every file from the storage engine for file in media.files: file.storage.delete(file.unique_id) # Remove this item from the DBSession so that the foreign key # ON DELETE CASCADE can take effect. DBSession.expunge(file) # Delete the media DBSession.delete(media) DBSession.flush() # Cleanup the thumbnails delete_thumbs(media)
def save_thumb(self, id, thumb, **kwargs): """Save a thumbnail uploaded with :class:`~mediadrop.forms.admin.ThumbForm`. :param id: Media ID. If ``"new"`` a new Media stub is created. :type id: ``int`` or ``"new"`` :param file: The uploaded file :type file: :class:`cgi.FieldStorage` or ``None`` :rtype: JSON dict :returns: success bool message Error message, if unsuccessful id The :attr:`~mediadrop.model.media.Media.id` which is important if a new media has just been created. """ if id == 'new': media = Media() user = request.perm.user media.author = Author(user.display_name, user.email_address) media.title = os.path.basename(thumb.filename) media.slug = get_available_slug(Media, '_stub_' + media.title) DBSession.add(media) DBSession.flush() else: media = fetch_row(Media, id) try: # Create JPEG thumbs create_thumbs_for(media, thumb.file, thumb.filename) success = True message = None except IOError, e: success = False if id == 'new': DBSession.delete(media) if e.errno == 13: message = _('Permission denied, cannot write file') elif e.message == 'cannot identify image file': message = _('Unsupported image type: %s') \ % os.path.splitext(thumb.filename)[1].lstrip('.') elif e.message == 'cannot read interlaced PNG files': message = _('Interlaced PNGs are not supported.') else: raise
def save(self, id, delete=False, **kwargs): """Save changes or create a tag. See :class:`~mediadrop.forms.admin.settings.tags.TagForm` for POST vars. :param id: Tag ID :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') tag = fetch_row(Tag, id) if delete: DBSession.delete(tag) data = dict(success=True, id=tag.id) else: tag.name = kwargs['name'] tag.slug = get_available_slug(Tag, kwargs['slug'], tag) DBSession.add(tag) DBSession.flush() data = dict( success = True, id = tag.id, name = tag.name, slug = tag.slug, row = unicode(tag_row_form.display(tag=tag)), ) if request.is_xhr: return data else: redirect(action='index', id=None)
def save( self, id, slug, title, subtitle, author_name, author_email, description, details, feed, delete=None, **kwargs ): """Save changes or create a new :class:`~mediadrop.model.podcasts.Podcast` instance. Form handler the :meth:`edit` action and the :class:`~mediadrop.forms.admin.podcasts.PodcastForm`. Redirects back to :meth:`edit` after successful editing and :meth:`index` after successful deletion. """ podcast = fetch_row(Podcast, id) if delete: DBSession.delete(podcast) DBSession.commit() delete_thumbs(podcast) redirect(action="index", id=None) if not slug: slug = title if slug != podcast.slug: podcast.slug = get_available_slug(Podcast, slug, podcast) podcast.title = title podcast.subtitle = subtitle podcast.author = Author(author_name, author_email) podcast.description = description podcast.copyright = details["copyright"] podcast.category = details["category"] podcast.itunes_url = feed["itunes_url"] podcast.feedburner_url = feed["feedburner_url"] podcast.explicit = {"yes": True, "clean": False}.get(details["explicit"], None) if id == "new": DBSession.add(podcast) DBSession.flush() create_default_thumbs_for(podcast) redirect(action="edit", id=podcast.id)
def save(self, id, email_address, display_name, login_details, delete=None, **kwargs): """Save changes or create a new :class:`~mediadrop.model.auth.User` instance. :param id: User ID. If ``"new"`` a new user is created. :type id: ``int`` or ``"new"`` :returns: Redirect back to :meth:`index` after successful save. """ user = fetch_row(User, id) if delete: DBSession.delete(user) redirect(action='index', id=None) user.display_name = display_name user.email_address = email_address user.user_name = login_details['user_name'] password = login_details['password'] if password is not None and password != '': user.password = password if login_details['groups']: query = DBSession.query(Group).filter(Group.group_id.in_(login_details['groups'])) user.groups = list(query.all()) else: user.groups = [] DBSession.add(user) # Check if we're changing the logged in user's own password if user.id == request.perm.user.id \ and password is not None and password != '': DBSession.commit() # repoze.who sees the Unauthorized response and clears the cookie, # forcing a fresh login with the new password raise webob.exc.HTTPUnauthorized().exception redirect(action='index', id=None)
def save(self, id, display_name, group_name, permissions, delete=None, **kwargs): """Save changes or create a new :class:`~mediadrop.model.auth.Group` instance. :param id: Group ID. If ``"new"`` a new group is created. :type id: ``int`` or ``"new"`` :returns: Redirect back to :meth:`index` after successful save. """ group = fetch_row(Group, id) if delete: DBSession.delete(group) redirect(action='index', id=None) group.display_name = display_name group.group_name = group_name if permissions: query = DBSession.query(Permission).filter(Permission.permission_id.in_(permissions)) group.permissions = list(query.all()) else: group.permissions = [] DBSession.add(group) redirect(action='index', id=None)
def delete(self, id, **kwargs): """Delete a user. :param id: User ID. :type id: ``int`` :returns: Redirect back to :meth:`index` after successful delete. """ user = fetch_row(User, id) all_deletes = dict( comments=Comment.query.filter( Comment.author_name == user.user_name), podcasts=Podcast.query.filter( Podcast.author_name == user.user_name), medias=Media.query.filter(Media.author_name == user.user_name)) for need_delete in all_deletes: print(need_delete) for element in all_deletes[need_delete]: DBSession.delete(element) DBSession.delete(user) if request.is_xhr: return dict(success=True) redirect(action='index', id=None)
except IOError, e: success = False if id == 'new': DBSession.delete(media) if e.errno == 13: message = _('Permission denied, cannot write file') elif e.message == 'cannot identify image file': message = _('Unsupported image type: %s') \ % os.path.splitext(thumb.filename)[1].lstrip('.') elif e.message == 'cannot read interlaced PNG files': message = _('Interlaced PNGs are not supported.') else: raise except Exception: if id == 'new': DBSession.delete(media) raise return dict( success=success, message=message, id=media.id, title=media.title, slug=media.slug, link=url_for(action='edit', id=media.id), ) @expose('json', request_method='POST') @validate(update_status_form, error_handler=edit) @autocommit @observable(events.Admin.MediaController.update_status)
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 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)
except IOError, e: success = False if id == 'new': DBSession.delete(media) if e.errno == 13: message = _('Permission denied, cannot write file') elif e.message == 'cannot identify image file': message = _('Unsupported image type: %s') \ % os.path.splitext(thumb.filename)[1].lstrip('.') elif e.message == 'cannot read interlaced PNG files': message = _('Interlaced PNGs are not supported.') else: raise except Exception: if id == 'new': DBSession.delete(media) raise return dict( success = success, message = message, id = media.id, title = media.title, slug = media.slug, link = url_for(action='edit', id=media.id), ) @expose('json', request_method='POST') @validate(update_status_form, error_handler=edit) @autocommit
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 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 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)
class MediaController(BaseController): @expose_xhr('users/media/index.html', 'users/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. """ user = request.perm.user.display_name media = Media.query.options(orm.undefer('comment_count_published')) media = media.filter(Media.author_name == user) if search: media = media.admin_search(search) else: media = media.order_by_status()\ .order_by(Media.publish_on.desc(), Media.modified_on.desc()) if 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('users/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 """ user = request.perm.user.display_name media = fetch_row(Media, id) if id != 'new': if media.author_name != user: media = fetch_row(Media, 'NONE') # Pull the defaults from the media item 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('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 = file.filename 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_xhr(request_method='POST') @validate_xhr(media_form, error_handler=edit) @autocommit @observable(events.Admin.MediaController.save) def save(self, id, slug, title, 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) author_name = media.author_name author_email = media.author_email 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 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') @autocommit @observable(events.Admin.MediaController.save_thumb) def save_thumb(self, id, thumb=None, **kwargs): """Save a thumbnail uploaded with :class:`~mediadrop.forms.admin.ThumbForm`. :param id: Media ID. If ``"new"`` a new Media stub is created. :type id: ``int`` or ``"new"`` :param file: The uploaded file :type file: :class:`cgi.FieldStorage` or ``None`` :rtype: JSON dict :returns: success bool message Error message, if unsuccessful id The :attr:`~mediadrop.model.media.Media.id` which is important if a new media has just been created. """ user = request.perm.user if id == 'new': media = Media() user = request.perm.user media.author = Author(user.display_name, user.email_address) media.title = os.path.basename(kwargs['file'].filename) media.slug = get_available_slug(Media, '_stub_' + media.title) DBSession.add(media) DBSession.flush() else: media = fetch_row(Media, id) group = request.perm.user.groups[0].group_name if media.author.name != user.display_name and group != 'admins': raise try: # Create JPEG thumbs create_thumbs_for(media, kwargs['file'].file, kwargs['file'].filename) success = True message = None except IOError, e: success = False if id == 'new': DBSession.delete(media) if e.errno == 13: message = _('Permission denied, cannot write file') elif e.message == 'cannot identify image file': message = _('Unsupported image type: %s') \ % os.path.splitext(kwargs['file'].filename)[1].lstrip('.') elif e.message == 'cannot read interlaced PNG files': message = _('Interlaced PNGs are not supported.') else: raise except Exception: if id == 'new': DBSession.delete(media) raise