changeset 718:bf5340705d0c

Completed view to delete user photos. Still need to modify the admin to delete not just the model instance but the S3 bucket keys.
author Brian Neal <bgneal@gmail.com>
date Wed, 18 Sep 2013 21:34:05 -0500
parents 846cf9a06a04
children cc8de231df5a
files core/s3.py sg101/templates/user_photos/gallery.html sg101/templates/user_photos/photo_detail.html user_photos/s3.py user_photos/views.py
diffstat 5 files changed, 118 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/core/s3.py	Wed Sep 18 18:33:52 2013 -0500
+++ b/core/s3.py	Wed Sep 18 21:34:05 2013 -0500
@@ -10,8 +10,6 @@
 class S3Bucket(object):
     """This class abstracts an Amazon S3 bucket.
 
-    We currently only support upload functionality.
-
     """
     def __init__(self, access_key, secret_key, base_url, bucket_name):
         self.conn = S3Connection(access_key, secret_key)
@@ -71,6 +69,28 @@
             key.make_public()
         return '{}{}/{}'.format(self.base_url, self.name, key_name)
 
+    def delete_keys(self, key_urls):
+        """Deletes a set of keys, specified as a list of URLs. The URLs could
+        have been returned by one or more of the upload_* methods.
+
+        Returns the number of keys that were successfully deleted.
+
+        """
+        if len(key_urls) == 0:
+            return 0
+
+        prefix = '{}{}/'.format(self.base_url, self.name)
+        prefix_len = len(prefix)
+
+        keys = []
+        for url in key_urls:
+            if url.startswith(prefix):
+                key = url[prefix_len:]
+                keys.append(key)
+
+        response = self.bucket.delete_keys(keys, quiet=True)
+        return len(key_urls) - len(response.errors)
+
     def _make_key(self, key_name, metadata):
         """Private method to create a key and optionally apply metadata to
         it.
--- a/sg101/templates/user_photos/gallery.html	Wed Sep 18 18:33:52 2013 -0500
+++ b/sg101/templates/user_photos/gallery.html	Wed Sep 18 21:34:05 2013 -0500
@@ -4,6 +4,34 @@
 {% block custom_css %}
 <link rel="stylesheet" href="{{ STATIC_URL }}css/user_photos.css" />
 {% endblock %}
+{% block custom_js %}
+{% if user == gallery_owner %}
+<script>
+function confirmPhotoDelete(submit)
+{
+   var n = $("#photo-delete input[type='checkbox']:checked").length;
+   if (n == 0)
+   {
+      alert("Please select some photos to delete.");
+      return false;
+   }
+
+   var msg = [
+      "Are you sure you want to delete these photos?\n",
+      "This will cause broken images in any posts you pasted them in. ",
+      "To fix this you can edit the posts."
+   ].join("");
+   var result = confirm(msg);
+   if (result)
+   {
+      submit.disabled = true;
+      submit.value = "Please wait...";
+   }
+   return result;
+}
+</script>
+{% endif %}
+{% endblock %}
 {% block content %}
 
 <h2>Photo Gallery for {{ gallery_owner.username }}</h2>
@@ -27,7 +55,8 @@
 
 {% if photos %}
    {% if user == gallery_owner %}
-      <form action="{% url 'user_photos-delete' %}" method="post">{% csrf_token %}
+      <form id="photo-delete" action="{% url 'user_photos-delete' %}" method="post"
+         onsubmit="return confirmPhotoDelete(submit);" >{% csrf_token %}
       {% for photo in photos %}
          <div class="user_photo owner_photo">
             <a href="{{ photo.get_absolute_url }}">
--- a/sg101/templates/user_photos/photo_detail.html	Wed Sep 18 18:33:52 2013 -0500
+++ b/sg101/templates/user_photos/photo_detail.html	Wed Sep 18 21:34:05 2013 -0500
@@ -61,7 +61,14 @@
        <a href="{% url 'user_photos-upload' %}">Upload another photo</a></li>
    <li><img src="{{ STATIC_URL }}icons/pictures.png" alt="Gallery" />
        <a href="{% url 'user_photos-gallery' username=object.user.username %}">See all my photos</a></li>
- </ul>
+</ul>
+
+<form id="photo-delete" action="{% url 'user_photos-delete' %}" method="post"
+         onsubmit="return confirm('Really delete this photo?');" >{% csrf_token %}
+   <input type="hidden" name="photo_id" value="{{ object.id }}"></input>
+   <input type="submit" name="submit" value="Delete Photo" />
+</form>
+
 {% else %}
 <p>
 <img src="{{ STATIC_URL }}icons/pictures.png" alt="Gallery" />
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/user_photos/s3.py	Wed Sep 18 21:34:05 2013 -0500
@@ -0,0 +1,40 @@
+"""Module for all S3 related operations for the user_photos application."""
+
+import logging
+
+from django.conf import settings
+
+from core.s3 import S3Bucket
+
+
+logger = logging.getLogger(__name__)
+
+
+def delete_photos(qs):
+    """Delete the photos stored on S3 for the given Photo queryset.
+
+    Returns the number of photos actually deleted.
+
+    """
+
+    bucket = S3Bucket(settings.USER_PHOTOS_ACCESS_KEY,
+                      settings.USER_PHOTOS_SECRET_KEY,
+                      settings.USER_PHOTOS_BASE_URL,
+                      settings.USER_PHOTOS_BUCKET)
+
+    key_urls = []
+    for photo in qs:
+        key_urls.append(photo.url)
+        key_urls.append(photo.thumb_url)
+    req_cnt = len(key_urls)
+
+    logger.info("Requesting deletion of %d user photo(s) from S3", req_cnt)
+
+    act_cnt = bucket.delete_keys(key_urls)
+
+    if act_cnt == req_cnt:
+        logger.info("Deleted %d user photo(s) from S3", act_cnt)
+    else:
+        logger.warning("Deleted %d user photo(s) out of %d", act_cnt, req_cnt)
+
+    return act_cnt
--- a/user_photos/views.py	Wed Sep 18 18:33:52 2013 -0500
+++ b/user_photos/views.py	Wed Sep 18 21:34:05 2013 -0500
@@ -10,6 +10,7 @@
 
 from user_photos.forms import UploadForm
 from user_photos.models import Photo
+from user_photos.s3 import delete_photos
 
 
 @login_required
@@ -69,6 +70,14 @@
 @login_required
 @require_POST
 def delete(request):
+    """A view function to allow a user to delete their own photos."""
+
+    ret_view, username = 'user_photos-gallery', request.user.username
+
+    if not settings.USER_PHOTOS_ENABLED:
+        messages.error(request, "This function is disabled temporarily")
+        return redirect(ret_view, username)
+
     photo_ids = []
     for photo_id in request.POST.getlist('photo_id'):
         try:
@@ -80,11 +89,14 @@
     count = 0
     if photo_ids:
         qs = Photo.objects.filter(user=request.user, pk__in=photo_ids)
-        count = qs.count()
-        qs.delete()
+        count = len(qs)
+        if count:
+            delete_photos(qs)
+            qs.delete()
 
-    if count:
-        msg = "{} photo{} deleted".format(count, '' if count == 1 else 's')
-        messages.add_message(request, messages.INFO, msg)
+    msg = "{} photo{} deleted".format(count, '' if count == 1 else 's')
+    messages.add_message(request,
+            messages.SUCCESS if count > 0 else messages.WARNING,
+            msg)
 
-    return redirect('user_photos-gallery', username=request.user.username)
+    return redirect(ret_view, username)