=== modified file 'dashboard_app/admin.py'
@@ -22,6 +22,7 @@
from django import forms
from django.contrib import admin
+from django.contrib.admin.actions import delete_selected
from django.contrib.contenttypes import generic
from django.utils.translation import ugettext as _
@@ -46,12 +47,6 @@
TestingEffort,
)
-def delete_bundle_action(modeladmin, request, queryset):
- for bundle in queryset:
- bundle.delete_files()
- bundle.delete()
-delete_bundle_action.short_description = "Delete bundle and related data"
-
class BundleAdmin(admin.ModelAdmin):
@@ -59,7 +54,6 @@
return bundle.bundle_stream.pathname
bundle_stream_pathname.short_description = _("Bundle stream")
- actions = [delete_bundle_action]
list_display = ('bundle_stream_pathname', 'content_filename',
'uploaded_by', 'uploaded_on', 'is_deserialized')
list_filter = ('bundle_stream',)
@@ -93,7 +87,29 @@
return super(BundleStreamAdminForm, self).clean()
+def cleanup_bundle_stream_selected(modeladmin, request, queryset):
+ """
+ This action cleans up the bundles from a bundle stream, without remove
+ the bundle stream itself.
+ """
+ my_modeladmin = BundleAdmin(Bundle, modeladmin.admin_site)
+ my_modeladmin.delete_selected_confirmation_template = 'admin/dashboard_app/cleanup_selected_bundle_confirmation.html'
+ my_queryset = None
+ if request.POST.get('post'): # handle bundles
+ selected_bundles = request.POST.getlist('_selected_action')
+ my_queryset = Bundle.objects.filter(pk__in=selected_bundles)
+ else: # handle bundle streams
+ for bundle_stream in queryset:
+ if my_queryset is None:
+ my_queryset = bundle_stream.bundles.all()
+ else:
+ my_queryset = my_queryset | bundle_stream.bundles.all()
+ return delete_selected(my_modeladmin, request, my_queryset)
+cleanup_bundle_stream_selected.short_description = "Clean up selected %(verbose_name_plural)s"
+
+
class BundleStreamAdmin(admin.ModelAdmin):
+ actions = [cleanup_bundle_stream_selected]
form = BundleStreamAdminForm
list_display = ('pathname', 'user', 'group', 'slug', 'is_public', 'is_anonymous', 'name')
list_filter = ('is_public', 'is_anonymous')
=== modified file 'dashboard_app/models.py'
@@ -39,6 +39,9 @@
from django.core.files.storage import FileSystemStorage
from django.core.urlresolvers import reverse
from django.db import models
+from django.db.models.fields import FieldDoesNotExist
+from django.db.models.signals import post_delete
+from django.dispatch import receiver
from django.template import Template, Context
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
@@ -1502,3 +1505,32 @@
def __unicode__(self):
return unicode(self.bug_id)
+
+@receiver(post_delete)
+def file_cleanup(sender, instance, **kwargs):
+ """
+ Signal receiver used for remove FieldFile attachments when removing
+ objects (Bundle and Attachment) from the database.
+ """
+ if instance is None or sender not in (Bundle, Attachment):
+ return
+ meta = sender._meta
+
+ for field_name in meta.get_all_field_names():
+
+ # object that represents the metadata of the field
+ try:
+ field_meta = meta.get_field(field_name)
+ except FieldDoesNotExist:
+ continue
+
+ # we just want the FileField's, not all the fields
+ if not isinstance(field_meta, models.FileField):
+ continue
+
+ # the field itself is a FieldFile instance, proxied by FileField
+ field = getattr(instance, field_name)
+
+ # the 'path' attribute contains the name of the file we need
+ if hasattr(field, 'path') and os.path.exists(field.path):
+ field.storage.delete(field.path)
=== added directory 'dashboard_app/templates/admin'
=== added directory 'dashboard_app/templates/admin/dashboard_app'
=== added file 'dashboard_app/templates/admin/dashboard_app/cleanup_selected_bundle_confirmation.html'
@@ -0,0 +1,47 @@
+{% extends "admin/base_site.html" %}
+{% load i18n l10n %}
+
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+ <a href="../../">{% trans "Home" %}</a> ›
+ <a href="../">{{ app_label|capfirst }}</a> ›
+ <a href="./">{{ opts.verbose_name_plural|capfirst }}</a> ›
+ {% trans 'Delete multiple objects' %}
+</div>
+{% endblock %}
+
+{% block content %}
+{% if perms_lacking or protected %}
+ {% if perms_lacking %}
+ <p>{% blocktrans %}Deleting the selected {{ objects_name }} would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktrans %}</p>
+ <ul>
+ {% for obj in perms_lacking %}
+ <li>{{ obj }}</li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+ {% if protected %}
+ <p>{% blocktrans %}Deleting the selected {{ objects_name }} would require deleting the following protected related objects:{% endblocktrans %}</p>
+ <ul>
+ {% for obj in protected %}
+ <li>{{ obj }}</li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+{% else %}
+ <p>{% blocktrans %}Are you sure you want to delete the selected {{ objects_name }}? All of the following objects and their related items will be deleted:{% endblocktrans %}</p>
+ {% for deletable_object in deletable_objects %}
+ <ul>{{ deletable_object|unordered_list }}</ul>
+ {% endfor %}
+ <form action="" method="post">{% csrf_token %}
+ <div>
+ {% for obj in queryset %}
+ <input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}" />
+ {% endfor %}
+ <input type="hidden" name="action" value="cleanup_bundle_stream_selected" />
+ <input type="hidden" name="post" value="yes" />
+ <input type="submit" value="{% trans "Yes, I'm sure" %}" />
+ </div>
+ </form>
+{% endif %}
+{% endblock %}