def get_broken_media(): storage = get_media_storage() media_subdir = app_settings.MEDIA_TREE_UPLOAD_SUBDIR broken_nodes = [] orphaned_files = [] files_in_db = [] for node in FileNode.objects.filter(node_type=FileNode.FILE): path = node.file.path # need to normalize unicode path due to https://code.djangoproject.com/ticket/16315 path = normalize('NFC', path) files_in_db.append(path) if not storage.exists(node.file): broken_nodes.append(node) files_in_storage = [storage.path(os.path.join(media_subdir, filename)) \ for filename in storage.listdir(media_subdir)[1]] for file_path in files_in_storage: # need to normalize unicode path due to https://code.djangoproject.com/ticket/16315 file_path = normalize('NFC', file_path) if not file_path in files_in_db: storage_name = os.path.join(media_subdir, os.path.basename(file_path)) orphaned_files.append(storage_name) return [broken_nodes, orphaned_files]
def clear_cache(modeladmin, request, queryset=None): """ """ from unicodedata import normalize execute = request.POST.get('execute') files_in_storage = [] storage = get_media_storage() cache_files_choices = [] for cache_dir in get_media_backend().get_cache_paths(): if storage.exists(cache_dir): files_in_dir = [storage.path(os.path.join(cache_dir, filename)) \ for filename in storage.listdir(cache_dir)[1]] for file_path in files_in_dir: # need to normalize unicode path due to https://code.djangoproject.com/ticket/16315 file_path = normalize('NFC', file_path) storage_name = os.path.join(cache_dir, os.path.basename(file_path)) link = mark_safe('<a href="%s">%s</a>' % ( storage.url(storage_name), storage_name)) cache_files_choices.append((storage_name, link)) if not len(cache_files_choices): #request.user.message_set.create(message=_('There are no cache files.')) return HttpResponseRedirect('') if execute: form = DeleteCacheFilesForm(queryset, cache_files_choices, request.POST) if form.is_valid(): form.save() node = FileNode.get_top_node() message = ungettext('Deleted %i cache file.', 'Deleted %i cache files.', len(form.success_files)) % len(form.success_files) if len(form.success_files) == len(cache_files_choices): message = '%s %s' % (_('The cache was cleared.'), message) #request.user.message_set.create(message=message) if form.error_files: pass #request.user.message_set.create(message=_('The following files could not be deleted:')+' '+repr(form.error_files)) return HttpResponseRedirect(node.get_admin_url()) if not execute: if len(cache_files_choices) > 0: form = DeleteCacheFilesForm(queryset, cache_files_choices) else: form = None c = get_actions_context(modeladmin) c.update({ 'title': _('Clear cache'), 'submit_label': _('Delete selected files'), 'form': form, 'select_all': 'selected_files', }) return render_to_response('admin/media_tree/filenode/actions_form.html', c, context_instance=RequestContext(request)) return HttpResponseRedirect('')
def handle(self, *args, **options): cache_files = get_cache_files() storage = get_media_storage() for path in cache_files: if options['delete']: storage.delete(path) self.stdout.write("Deleted %s\n" % storage.path(path)) else: self.stdout.write("%s\n" % storage.path(path))
def handle(self, *args, **options): orphaned_files = get_orphaned_files() storage = get_media_storage() for path in orphaned_files: if options["delete"]: storage.delete(path) self.stdout.write("Deleted %s\n" % storage.path(path)) else: self.stdout.write("%s\n" % storage.path(path))
def clear_cache(modeladmin, request, queryset=None): """ Clears media cache files such as thumbnails. """ execute = request.POST.get('execute') files_in_storage = [] storage = get_media_storage() cache_files_choices = [] for storage_name in get_cache_files(): link = mark_safe('<a href="%s">%s</a>' % (storage.url(storage_name), storage_name)) cache_files_choices.append((storage_name, link)) if not len(cache_files_choices): messages.warning(request, message=_('There are no cache files.')) return HttpResponseRedirect('') if execute: form = DeleteCacheFilesForm(queryset, cache_files_choices, request.POST) if form.is_valid(): form.save() node = FileNode.get_top_node() message = ungettext( 'Deleted %i cache file.', 'Deleted %i cache files.', len(form.success_files)) % len(form.success_files) if len(form.success_files) == len(cache_files_choices): message = '%s %s' % (_('The cache was cleared.'), message) messages.success(request, message=message) if form.error_files: messages.error( request, message=_('The following files could not be deleted:') + ' ' + repr(form.error_files)) return HttpResponseRedirect(node.get_admin_url()) if not execute: if len(cache_files_choices) > 0: form = DeleteCacheFilesForm(queryset, cache_files_choices) else: form = None c = get_actions_context(modeladmin) c.update({ 'title': _('Clear cache'), 'submit_label': _('Delete selected files'), 'form': form, 'select_all': 'selected_files', }) return render_to_response('admin/media_tree/filenode/actions_form.html', c, context_instance=RequestContext(request)) return HttpResponseRedirect('')
def clear_cache(modeladmin, request, queryset=None): """ Clears media cache files such as thumbnails. """ execute = request.POST.get('execute') files_in_storage = [] storage = get_media_storage() cache_files_choices = [] for storage_name in get_cache_files(): link = mark_safe('<a href="%s">%s</a>' % ( storage.url(storage_name), storage_name)) cache_files_choices.append((storage_name, link)) if not len(cache_files_choices): messages.warning(request, message=_('There are no cache files.')) return HttpResponseRedirect('') if execute: form = DeleteCacheFilesForm( queryset, cache_files_choices, request.POST) if form.is_valid(): form.save() node = FileNode.get_top_node() message = ungettext( 'Deleted %i cache file.', 'Deleted %i cache files.', len(form.success_files)) % len(form.success_files) if len(form.success_files) == len(cache_files_choices): message = '%s %s' % (_('The cache was cleared.'), message) messages.success(request, message=message) if form.error_files: messages.error( request, message=_('The following files could not be deleted:') + ' ' + repr(form.error_files)) return HttpResponseRedirect(node.get_admin_url()) if not execute: if len(cache_files_choices) > 0: form = DeleteCacheFilesForm(queryset, cache_files_choices) else: form = None c = get_actions_context(modeladmin) c.update({ 'title': _('Clear cache'), 'submit_label': _('Delete selected files'), 'form': form, 'select_all': 'selected_files', }) return render_to_response('admin/media_tree/filenode/actions_form.html', c, context_instance=RequestContext(request)) return HttpResponseRedirect('')
def save(self): """ Deletes the selected files from storage """ storage = get_media_storage() for storage_name in self.cleaned_data['selected_files']: full_path = storage.path(storage_name) try: storage.delete(storage_name) self.success_files.append(full_path) except OSError: self.error_files.append(full_path)
def delete_orphaned_files(modeladmin, request, queryset=None): """ Deletes orphaned files, i.e. media files existing in storage that are not in the database. """ execute = request.POST.get('execute') storage = get_media_storage() broken_node_links = [] orphaned_files_choices = [] broken_nodes, orphaned_files = get_broken_media() for node in broken_nodes: link = mark_safe('<a href="%s">%s</a>' % (node.get_admin_url(), node.__unicode__())) broken_node_links.append(link) for storage_name in orphaned_files: file_path = storage.path(storage_name) link = mark_safe('<a href="%s">%s</a>' % ( storage.url(storage_name), file_path)) orphaned_files_choices.append((storage_name, link)) if not len(orphaned_files_choices) and not len(broken_node_links): messages.success(request, message=_('There are no orphaned files.')) return HttpResponseRedirect('') if execute: form = DeleteOrphanedFilesForm(queryset, orphaned_files_choices, request.POST) if form.is_valid(): form.save() node = FileNode.get_top_node() messages.success(request, message=ungettext('Deleted %i file from storage.', 'Deleted %i files from storage.', len(form.success_files)) % len(form.success_files)) if form.error_files: messages.error(request, message=_('The following files could not be deleted from storage:')+' '+repr(form.error_files)) return HttpResponseRedirect(node.get_admin_url()) if not execute: if len(orphaned_files_choices) > 0: form = DeleteOrphanedFilesForm(queryset, orphaned_files_choices) else: form = None c = get_actions_context(modeladmin) c.update({ 'title': _('Orphaned files'), 'submit_label': _('Delete selected files'), 'form': form, 'select_all': 'selected_files', 'node_list_title': _('The following files in the database do not exist in storage. You should fix these media objects:'), 'node_list': broken_node_links, }) return render_to_response('admin/media_tree/filenode/actions_form.html', c, context_instance=RequestContext(request))
def get_cache_files(): storage = get_media_storage() cache_files = [] for cache_dir in get_media_backend().get_cache_paths(): if storage.exists(cache_dir): files_in_dir = [storage.path(os.path.join(cache_dir, filename)) \ for filename in storage.listdir(cache_dir)[1]] for file_path in files_in_dir: # need to normalize unicode path due to https://code.djangoproject.com/ticket/16315 file_path = normalize('NFC', file_path) storage_name = os.path.join(cache_dir, os.path.basename(file_path)) cache_files.append(storage_name) return cache_files
def delete_orphaned_files(modeladmin, request, queryset=None): from unicodedata import normalize execute = request.POST.get("execute") media_subdir = app_settings.MEDIA_TREE_UPLOAD_SUBDIR files_in_storage = [] storage = get_media_storage() files_in_db = [] nodes_with_missing_file_links = [] for node in FileNode.objects.filter(node_type=FileNode.FILE): path = node.file.path # need to normalize unicode path due to https://code.djangoproject.com/ticket/16315 path = normalize("NFC", path) files_in_db.append(path) if not storage.exists(node.file): link = mark_safe('<a href="%s">%s</a>' % (node.get_admin_url(), node.__unicode__())) nodes_with_missing_file_links.append(link) files_in_storage = [ storage.path(os.path.join(media_subdir, filename)) for filename in storage.listdir(media_subdir)[1] ] orphaned_files_choices = [] for file_path in files_in_storage: # need to normalize unicode path due to https://code.djangoproject.com/ticket/16315 file_path = normalize("NFC", file_path) if not file_path in files_in_db: storage_name = os.path.join(media_subdir, os.path.basename(file_path)) link = mark_safe('<a href="%s">%s</a>' % (storage.url(storage_name), file_path)) orphaned_files_choices.append((storage_name, link)) if not len(orphaned_files_choices) and not len(nodes_with_missing_file_links): request.user.message_set.create(message=_("There are no orphaned files.")) return HttpResponseRedirect("") if execute: form = DeleteOrphanedFilesForm(queryset, orphaned_files_choices, request.POST) if form.is_valid(): form.save() node = FileNode.get_top_node() request.user.message_set.create( message=ungettext( "Deleted %i file from storage.", "Deleted %i files from storage.", len(form.success_files) ) % len(form.success_files) ) if form.error_files: request.user.message_set.create( message=_("The following files could not be deleted from storage:") + " " + repr(form.error_files) ) return HttpResponseRedirect(node.get_admin_url()) if not execute: if len(orphaned_files_choices) > 0: form = DeleteOrphanedFilesForm(queryset, orphaned_files_choices) else: form = None c = get_actions_context(modeladmin) c.update( { "title": _("Orphaned files"), "submit_label": _("Delete selected files"), "form": form, "select_all": "selected_files", "node_list_title": _( "The following files in the database do not exist in storage. You should fix these media objects:" ), "node_list": nodes_with_missing_file_links, } ) return render_to_response( "admin/media_tree/filenode/actions_form.html", c, context_instance=RequestContext(request) )
class FileNode(ModelBase): """ Each ``FileNode`` instance represents a node in the media object tree, that is to say a “file” or “folder”. Accordingly, their ``node_type`` attribute can either be ``FileNode.FOLDER``, meaning that they may have child nodes, or ``FileNode.FILE``, meaning that they are associated to media files in storage and are storing metadata about those files. .. Note:: Since ``FileNode`` is a child class of ``MPTTModel``, it inherits many methods that facilitate queries and data manipulation when working with trees. You can access the actual media associated to a ``FileNode`` model instance using the following fields: .. role:: descname(literal) :class: descname :descname:`file` The actual media file :descname:`preview_file` An optional image file that will be used for previews. This is useful for visual media that PIL cannot read, such as video files. These fields are of the class ``FileField``. Please see :ref:`configuration` for information on how to configure storage and media backend classes. By default, media files are stored in a subfolder ``uploads`` under your media root. """ FOLDER = media_types.FOLDER """ The constant denoting a folder node, used for the :attr:`node_type` attribute. """ FILE = media_types.FILE """ The constant denoting a file node, used for the :attr:`node_type` attribute. """ STORAGE = get_media_storage() tree = TreeManager() """ MPTT tree manager """ objects = FileNodeManager() """ An instance of the :class:`FileNodeManager` class, providing methods for retrieving ``FileNode`` objects by their full node path. """ published_objects = FileNodeManager({'published': True}) """ A special manager with the same features as :attr:`objects`, but only displaying currently published objects. """ folders = FileNodeManager({'node_type': FOLDER}) """ A special manager with the same features as :attr:`objects`, but only displaying folder nodes. """ files = FileNodeManager({'node_type': FILE}) """ A special manager with the same features as :attr:`objects`, but only displaying file nodes, no folder nodes. """ # FileFields -- have no docstring since Sphinx cannot access these attributes file = models.FileField(_('file'), upload_to=app_settings.MEDIA_TREE_UPLOAD_SUBDIR, null=True, storage=STORAGE) # The actual media file preview_file = models.ImageField(_('preview'), upload_to=app_settings.MEDIA_TREE_PREVIEW_SUBDIR, blank=True, null=True, help_text=_('Use this field to upload a preview image for video or similar media types.'), storage=STORAGE) # An optional image file that will be used for previews. This is useful for video files. parent = models.ForeignKey('self', null=True, blank=True, related_name='children', verbose_name=_('folder'), limit_choices_to={'node_type': FOLDER}) """ The parent (folder) object of the node. """ node_type = models.IntegerField(_('node type'), choices = ((FOLDER, 'Folder'), (FILE, 'File')), editable=False, blank=False, null=False) """ Type of the node (:attr:`FileNode.FILE` or :attr:`FileNode.FOLDER`) """ media_type = models.IntegerField(_('media type'), choices = app_settings.MEDIA_TREE_CONTENT_TYPE_CHOICES, blank=True, null=True, editable=False) """ Media type, i.e. broad category of the kind of media """ allowed_child_node_types = MultipleChoiceCommaSeparatedIntegerField(_('allowed '), choices = ((FILE, _('file')), (FOLDER, _('folder'))), blank=True, null=True, max_length=64, help_text=_('If none selected, all are allowed.')) """ Media type, i.e. broad category of the kind of media """ published = models.BooleanField(_('is published'), blank=True, default=True) """ Publish date and time """ mimetype = models.CharField(_('mimetype'), max_length=64, null=True, editable=False) """ The mime type of the media file """ name = models.CharField(_('name'), max_length=255, null=True) """ Name of the file or folder """ title = models.CharField(_('title'), max_length=255, default='', null=True, blank=True) """ Title for the file """ description = models.TextField(_('description'), default='', null=True, blank=True) """ Description for the file """ author = models.CharField(_('author'), max_length=255, default='', null=True, blank=True) """ Author name of the file """ publish_author = models.BooleanField(_('publish author'), default=False) """ Flag to toggle whether the author name should be displayed """ copyright = models.CharField(_('copyright'), max_length=255, default='', null=True, blank=True) """ Copyright information for the file """ publish_copyright = models.BooleanField(_('publish copyright'), default=False) """ Flag to toggle whether copyright information should be displayed """ date_time = models.DateTimeField(_('date/time'), null=True, blank=True) """ Date and time information for the file (authoring or publishing date) """ publish_date_time = models.BooleanField(_('publish date/time'), default=False) """ Flag to toggle whether date and time information should be displayed """ keywords = models.CharField(_('keywords'), max_length=255, null=True, blank=True) """ Keywords for the file """ override_alt = models.CharField(_('alternative text'), max_length=255, default='', null=True, blank=True, help_text=_('If you leave this blank, the alternative text will be compiled automatically from the available metadata.')) """ Alt text override. If empty, the alt text will be compiled from the all metadata that is available and flagged to be displayed. """ override_caption = models.CharField(_('caption'), max_length=255, default='', null=True, blank=True, help_text=_('If you leave this blank, the caption will be compiled automatically from the available metadata.')) """ Caption override. If empty, the caption will be compiled from the all metadata that is available and flagged to be displayed. """ has_metadata = models.BooleanField(_('metadata entered'), editable=False) """ Flag specifying whether the absolute minimal metadata was entered """ extension = models.CharField(_('type'), default='', max_length=10, null=True, editable=False) """ File extension, lowercase """ size = models.IntegerField(_('size'), null=True, editable=False) """ File size in bytes """ # TODO: Refactor PIL stuff, width|height as extension? width = models.IntegerField(_('width'), null=True, blank=True, help_text=_('Detected automatically for supported images')) """ For images: width in pixels """ height = models.IntegerField(_('height'), null=True, blank=True, help_text=_('Detected automatically for supported images')) """ For images: height in pixels """ slug = models.CharField(_('slug'), max_length=255, null=True, editable=False) """ Slug for the object """ is_default = models.BooleanField(_('use as default object for folder'), blank=True, default=False, help_text=_('The default object of a folder can be used for folder previews etc.')) """ Flag whether the file is the default file in its parent folder """ created = models.DateTimeField(_('created'), auto_now_add=True, editable=False) """ Date and time when object was created """ modified = models.DateTimeField(_('modified'), auto_now=True, editable=False) """ Date and time when object was last modified """ created_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, related_name='created_by', verbose_name = _('created by'), editable=False) """ User that created the object """ modified_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, related_name='modified_by', verbose_name = _('modified by'), editable=False) """ User that last modified the object """ position = models.IntegerField(_('position'), default=0, blank=True) """ Position of the file among its siblings, for manual ordering """ extra_metadata = models.TextField(_('extra metadata'), editable=None) """ Extra metadata """ is_ancestor_being_updated = False class Meta: ordering = ['tree_id', 'lft'] verbose_name = _('media object') verbose_name_plural = _('media objects') permissions = ( ("manage_filenode", "Can perform management tasks"), ) class MPTTMeta: order_insertion_by = ['name'] @staticmethod def get_top_node(): """Returns a symbolic node representing the root of all nodes. This node is not actually stored in the database, but used in the admin to link to the change list. """ return FileNode(name=('Media objects'), level=-1) def is_top_node(self): """Returns True if the model instance is the top node.""" return self.level == -1 # Workaround for http://code.djangoproject.com/ticket/11058 def admin_preview(self): pass # TODO: What's this for again? @Property def link(): def fget(self): return getattr(self, 'link_obj', None) def fset(self, link_obj): self.link_obj = link_obj def fdel(self): del self.link_obj return locals() def attach_user(self, user, change): if not change: self.created_by = user self.modified_by = user def get_node_path(self): nodes = [] for node in self.get_ancestors(): nodes.append(node) if (self.level != -1): nodes.insert(0, FileNode.get_top_node()) nodes.append(self) return nodes def get_folder_tree(self): return self._tree_manager.all().filter(node_type=FileNode.FOLDER) def get_default_file(self, media_types=None): if self.node_type == FileNode.FOLDER: if not media_types: files = self.get_children().filter(node_type=FileNode.FILE) else: files = self.get_children().filter(media_type__in=media_types) # TODO the two counts are due to the fact that, at this time, it seems # not possible to order the QuerySet returned by get_children() by is_default if files.count() > 0: default = files.filter(is_default=True) if default.count() > 0: return default[0] else: return files[0] else: return None else: return self def get_qualified_file_url(self, field_name='file'): """Returns a fully qualified URL for the :attr:`file` field, including protocol, domain and port. In most cases, you can just use ``file.url`` instead, which (depending on your ``MEDIA_URL``) may or may not contain the domain. In some cases however, you always need a fully qualified URL. This includes, for instance, embedding a flash video player from a remote domain and passing it a video URL. """ url = getattr(self, field_name).url if '://' in url: # `MEDIA_URL` already contains domain return url protocol = getattr(settings, 'PROTOCOL', 'http') domain = Site.objects.get_current().domain port = getattr(settings, 'PORT', '') return '%(protocol)s://%(domain)s%(port)s%(url)s' % { 'protocol': 'http', 'domain': domain.rstrip('/'), 'port': ':'+port if port else '', 'url': url, } def get_qualified_preview_url(self): """Similar to :func:`get_qualified_file_url`, but returns the URL for the :attr:`preview_file` field, which can be used to associate image previews with video files. """ return self.get_qualified_file_url('preview_file') def get_preview_file(self, default_name=None): if self.preview_file: return self.preview_file elif self.is_web_image(): return self.file else: return self.get_icon_file(default_name=default_name) def get_icon_file(self, default_name=None): if not default_name: default_name = '_blank' if not self.is_folder() else '_folder' for finder in ICON_FINDERS: icon_file = finder.find(self, default_name=default_name) if icon_file: return icon_file def get_media_type_name(self): return MEDIA_TYPE_NAMES[self.media_type] def is_descendant_of(self, ancestor_nodes): if issubclass(ancestor_nodes.__class__, FileNode): ancestor_nodes = (ancestor_nodes,) # Check whether requested folder is in selected nodes is_descendant = self in ancestor_nodes if not is_descendant: # Check whether requested folder is a subfolder of selected nodes ancestors = self.get_ancestors(ascending=True) if ancestors: self.parent_folder = ancestors[0] for ancestor in ancestors: if ancestor in ancestor_nodes: is_descendant = True break return is_descendant def get_descendant_count_display(self): if self.node_type == FileNode.FOLDER: return self.get_descendant_count() else: return '' get_descendant_count_display.short_description = _('Items') def has_metadata_including_descendants(self): if self.node_type == FileNode.FOLDER: count = self.get_descendants().filter(has_metadata=False).count() return count == 0 else: return self.has_metadata has_metadata_including_descendants.short_description = _('Metadata') has_metadata_including_descendants.boolean = True def get_path(self): path = '' for name in [node.name for node in self.get_ancestors()]: path = '%s%s/' % (path, name) return '%s%s' % (path, self.name) def get_admin_url(self, query_params=None, use_path=False): """Returns the URL for viewing a FileNode in the admin.""" if not query_params: query_params = {} url = '' if self.is_top_node(): url = reverse('admin:media_tree_filenode_changelist'); elif use_path and (self.is_folder() or self.pk): url = reverse('admin:media_tree_filenode_open_path', args=(self.get_path(),)); elif self.is_folder(): url = reverse('admin:media_tree_filenode_changelist'); query_params['folder_id'] = self.pk elif self.pk: return reverse('admin:media_tree_filenode_change', args=(self.pk,)); if len(query_params): params = ['%s=%s' % (key, value) for key, value in query_params.items()] url = '%s?%s' % (url, "&".join(params)) return url def get_admin_link(self): return force_unicode(mark_safe(u'%s: <a href="%s">%s</a>' % (capfirst(self._meta.verbose_name), self.get_admin_url(), self.__unicode__()))) @staticmethod def get_mimetype(filename, fallback_type='application/x-unknown'): ext = os.path.splitext(filename)[1].lstrip('.').lower() if ext in EXT_MIMETYPE_MAP: return EXT_MIMETYPE_MAP[ext] else: mimetype, encoding = mimetypes.guess_type(filename, strict=False) if mimetype: return mimetype else: return fallback_type @property def mime_supertype(self): if self.mimetype: return self.mimetype.split('/')[0] @property def mime_subtype(self): if self.mimetype: return self.mimetype.split('/')[1] @staticmethod def mimetype_to_media_type(filename): mimetype = FileNode.get_mimetype(filename) if mimetype: if MIMETYPE_CONTENT_TYPE_MAP.has_key(mimetype): return MIMETYPE_CONTENT_TYPE_MAP[mimetype] else: type, subtype = mimetype.split('/') if MIMETYPE_CONTENT_TYPE_MAP.has_key(type): return MIMETYPE_CONTENT_TYPE_MAP[type] return media_types.FILE # TODO: Move to extension def resolution_formatted(self): if self.width and self.height: return _(u'%(width)i×%(height)i') % {'width': self.width, 'height': self.height} else: return '' resolution_formatted.short_description = _('Resolution') resolution_formatted.admin_order_field = 'width' def make_name_unique_numbered(self, name, ext=''): # If file with same name exists in folder: qs = FileNode.objects.filter(parent=self.parent) if self.pk: qs = qs.exclude(pk=self.pk) number = 1 while qs.filter(name__exact=self.name).count() > 0: number += 1 # rename using a number self.name = app_settings.MEDIA_TREE_NAME_UNIQUE_NUMBERED_FORMAT % {'name': name, 'number': number, 'ext': ext} def prevent_save(self): self.save_prevented = True def save(self, *args, **kwargs): if getattr(self, 'save_prevented', False): raise ValidationError('Saving was presented for this FileNode object.') if self.node_type == FileNode.FOLDER: self.media_type = FileNode.FOLDER # Admin asserts that folder name is unique under parent. For other inserts: self.make_name_unique_numbered(self.name) else: # TODO: If file was not changed, this field will nevertheless be changed to # the name of the renamed file on disk. Do not do this unless a new file is being saved. file_changed = True if self.pk: try: saved_instance = FileNode.objects.get(pk=self.pk) if saved_instance.file == self.file: file_changed = False except FileNode.DoesNotExist: pass if file_changed: self.name = os.path.basename(self.file.name) # using os.path.splitext(), foo.tar.gz would become foo.tar_2.gz instead of foo_2.tar.gz split = multi_splitext(self.name) self.make_name_unique_numbered(split[0], split[1]) # Determine various file parameters self.size = self.file.size self.extension = split[2].lstrip('.').lower() self.width, self.height = (None, None) self.file.name = self.name # TODO: A hash (created by storage class!) would be great because it would obscure file # names, but it would be inconvenient for downloadable files # self.file.name = str(uuid.uuid4()) + '.' + self.extension # Determine whether file is a supported image: try: self.media_type = None self.pre_save_image() except IOError: pass if not self.media_type: self.media_type = FileNode.mimetype_to_media_type(self.name) self.slug = slugify(self.name) self.has_metadata = self.check_minimal_metadata() super(FileNode, self).save(*args, **kwargs) # TODO: Move to extension def pre_save_image(self): if self.extension in app_settings.MEDIA_TREE_VECTOR_EXTENSIONS: self.media_type = media_types.VECTOR_IMAGE else: self.saved_image = Image.open(self.file) self.media_type = media_types.SUPPORTED_IMAGE self.width, self.height = self.saved_image.size def file_path(self): return self.file.path if self.file else '' def is_folder(self): return self.node_type == FileNode.FOLDER def is_file(self): return self.node_type == FileNode.FILE def is_image(self): return self.media_type == media_types.SUPPORTED_IMAGE def is_web_image(self): return self.media_type in (media_types.SUPPORTED_IMAGE, media_types.VECTOR_IMAGE) def __unicode__(self): return self.name def check_minimal_metadata(self): result = (self.media_type in app_settings.MEDIA_TREE_METADATA_LESS_MEDIA_TYPES \ and self.name != '') or \ (self.title != '' or self.description != '' or \ self.override_alt != '' or self.override_caption != '') if result and self.node_type == FileNode.FOLDER and self.pk: result = self.has_metadata_including_descendants() return result def get_metadata_display(self, field_formats = {}, escape=True): """Returns object metadata that has been selected to be displayed to users, compiled as a string. """ def field_format(field): if field in field_formats: return field_formats[field] return u'%s' t = join_formatted('', self.title, format=field_format('title'), escape=escape) t = join_formatted(t, self.description, u'%s: %s', escape=escape) if self.publish_author: t = join_formatted(t, self.author, u'%s' + u' – ' + u'Author: %s', u'%s' + u'Author: %s', escape=escape) if self.publish_copyright: t = join_formatted(t, self.copyright, u'%s, %s', escape=escape) if self.publish_date_time and self.date_time: date_time_formatted = dateformat.format(self.date_time, get_format('DATE_FORMAT')) t = join_formatted(t, date_time_formatted, u'%s (%s)', '%s%s', escape=escape) return t get_metadata_display.allow_tags = True def get_metadata_display_unescaped(self): """Returns object metadata that has been selected to be displayed to users, compiled as a string with the original field values left unescaped, i.e. the original field values may contain tags. """ return self.get_metadata_display(escape=False) get_metadata_display_unescaped.allow_tags = True def get_caption_formatted(self, field_formats = app_settings.MEDIA_TREE_METADATA_FORMATS, escape=True): """Returns object metadata that has been selected to be displayed to users, compiled as a string including default formatting, for example bold titles. You can use this method in templates where you want to output image captions. """ if self.override_caption != '': return self.override_caption else: return mark_safe(self.get_metadata_display(field_formats, escape=escape)) get_caption_formatted.allow_tags = True get_caption_formatted.short_description = _('displayed metadata') def get_caption_formatted_unescaped(self): """Returns object metadata that has been selected to be displayed to users, compiled as a string with the original field values left unescaped, i.e. the original field values may contain tags. """ return self.get_caption_formatted(escape=False) get_caption_formatted_unescaped.allow_tags = True get_caption_formatted_unescaped.short_description = _('displayed metadata') @property def alt(self): """Returns object metadata suitable for use as the HTML ``alt`` attribute. You can use this method in templates:: <img src="{{ node.file.url }}" alt="{{ node.alt }}" /> """ if self.override_alt != '' and self.override_alt is not None: return self.override_alt elif self.override_caption != '' and self.override_caption is not None: return self.override_caption else: return self.get_metadata_display()
def clear_cache(modeladmin, request, queryset=None): """ """ from unicodedata import normalize execute = request.POST.get("execute") files_in_storage = [] storage = get_media_storage() cache_files_choices = [] for cache_dir in get_media_backend().get_cache_paths(): if storage.exists(cache_dir): files_in_dir = [ storage.path(os.path.join(cache_dir, filename)) for filename in storage.listdir(cache_dir)[1] ] for file_path in files_in_dir: # need to normalize unicode path due to https://code.djangoproject.com/ticket/16315 file_path = normalize("NFC", file_path) storage_name = os.path.join(cache_dir, os.path.basename(file_path)) link = mark_safe('<a href="%s">%s</a>' % (storage.url(storage_name), storage_name)) cache_files_choices.append((storage_name, link)) if not len(cache_files_choices): messages.warning(request, message=_("There are no cache files.")) return HttpResponseRedirect("") if execute: form = DeleteCacheFilesForm(queryset, cache_files_choices, request.POST) if form.is_valid(): form.save() node = FileNode.get_top_node() message = ungettext("Deleted %i cache file.", "Deleted %i cache files.", len(form.success_files)) % len( form.success_files ) if len(form.success_files) == len(cache_files_choices): message = "%s %s" % (_("The cache was cleared."), message) messages.success(request, message=message) if form.error_files: messages.error( request, message=_("The following files could not be deleted:") + " " + repr(form.error_files) ) return HttpResponseRedirect(node.get_admin_url()) if not execute: if len(cache_files_choices) > 0: form = DeleteCacheFilesForm(queryset, cache_files_choices) else: form = None c = get_actions_context(modeladmin) c.update( { "title": _("Clear cache"), "submit_label": _("Delete selected files"), "form": form, "select_all": "selected_files", } ) return render_to_response( "admin/media_tree/filenode/actions_form.html", c, context_instance=RequestContext(request) ) return HttpResponseRedirect("")
def delete_orphaned_files(modeladmin, request, queryset=None): """ Deletes orphaned files, i.e. media files existing in storage that are not in the database. """ execute = request.POST.get('execute') storage = get_media_storage() broken_node_links = [] orphaned_files_choices = [] broken_nodes, orphaned_files = get_broken_media() for node in broken_nodes: link = mark_safe('<a href="%s">%s</a>' % (node.get_admin_url(), node.__unicode__())) broken_node_links.append(link) for storage_name in orphaned_files: file_path = storage.path(storage_name) link = mark_safe('<a href="%s">%s</a>' % (storage.url(storage_name), file_path)) orphaned_files_choices.append((storage_name, link)) if not len(orphaned_files_choices) and not len(broken_node_links): messages.success(request, message=_('There are no orphaned files.')) return HttpResponseRedirect('') if execute: form = DeleteOrphanedFilesForm(queryset, orphaned_files_choices, request.POST) if form.is_valid(): form.save() node = FileNode.get_top_node() messages.success( request, message=ungettext('Deleted %i file from storage.', 'Deleted %i files from storage.', len(form.success_files)) % len(form.success_files)) if form.error_files: messages.error( request, message= _('The following files could not be deleted from storage:') + ' ' + repr(form.error_files)) return HttpResponseRedirect(node.get_admin_url()) if not execute: if len(orphaned_files_choices) > 0: form = DeleteOrphanedFilesForm(queryset, orphaned_files_choices) else: form = None c = get_actions_context(modeladmin) c.update({ 'title': _('Orphaned files'), 'submit_label': _('Delete selected files'), 'form': form, 'select_all': 'selected_files', 'node_list_title': _('The following files in the database do not exist in storage. You should fix these media objects:' ), 'node_list': broken_node_links, }) return render_to_response('admin/media_tree/filenode/actions_form.html', c, context_instance=RequestContext(request))