1

Problem

I am able to upload images into S3, however the structure of the URL within my django app is not structured properly.
The URL appears like this: http://localhost:8000/plants/https://'%s.s3.amazonaws.com'%AWS_STORAGE_BUCKET_NAME/media/plant_images/image.jpeg
However, I want it to appear as https://opensprouts-dev-media.s3.amazonaws.com/media/plant_images/images.jpeg

enter image description here

Context
I have a model that allows me to upload multiple images. I think I need to rewrite the way I call for the image within my template, or within my model. After reading through Django documentation and other stackoverflow questions, I'm not sure how to resolve this issue.

settings.py/base.py

# MEDIA
# ------------------------------------------------------------------------------
USE_S3 = os.getenv('USE_S3') == 'TRUE'

AWS_ACCESS_KEY_ID = config('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = config('AWS_SECRET_ACCESS_KEY')
AWS_STORAGE_BUCKET_NAME = config('AWS_STORAGE_BUCKET_NAME')
AWS_QUERYSTRING_AUTH = config('AWS_QUERYSTRING_AUTH')
AWS_S3_CUSTOM_DOMAIN = config('AWS_S3_CUSTOM_DOMAIN')
AWS_S3_OBJECT_PARAMETERS = {
    'CacheControl': 'max-age=86400',
}
AWS_LOCATION = config('AWS_LOCATION')
PUBLIC_MEDIA_LOCATION = 'media'
AWS_QUERYSTRING_AUTH = False

MEDIA_URL = 'https://%s/%s/' % (AWS_S3_CUSTOM_DOMAIN, AWS_LOCATIN)
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

The section of my template that is calling for the URL {{ image.images.url }}

{% for image in plant.plant_images.all %}
{% if forloop.first %}
<div class="carousel-item active">
{% else %}
<div class="carousel-item">
{% endif %}
    <img src="{{ image.images.url }}" alt="{{ plant.name }}">
</div>
{% endfor %}
</div>

models.py

import datetime

from django.conf import settings
from django.db import models
from django.utils import timezone
from django.template.defaultfilters import slugify
from django.urls import reverse
from django_quill.fields import QuillField

# Create your models here.

class Plant(models.Model):
    name = models.CharField(max_length=120)
    slug = models.SlugField(null=False, unique=True)
    description = models.TextField()

    def __str__(self):
        return self.name

    def publish(self):
        self.published_date = timezone.now()
        self.save()

    def get_absolute_url(self):
        return reverse("plant_detail", kwargs={"slug": self.slug})

class PlantImage(models.Model):
    plant = models.ForeignKey(Plant, default=None, on_delete=models.CASCADE, related_name="plant_images")
    images = models.ImageField(upload_to = 'plant_images/')
 
    def __str__(self):
        return self.plant.name

views.py

from .models import Plant, PlantImage

def plant_index(request):
    plant_objects = Plant.objects.all()
    context = {'plant_objects': plant_objects}
    return render(request, 'plants/plant_index.html', context)

class PlantDetailView(DetailView):
    model = Plant
    template_name = 'plants/plant_detail.html'
    slug = 'slug'

    def get_context_data(self, **kwargs):
        context = super(PlantDetailView, self).get_context_data(**kwargs)
        context['plant_images'] = PlantImage.objects.all()
        return context

plant_detail = PlantDetailView.as_view()

I can see the images are successfully being stored in s3 after I upload them via my Django backend UI, however the URL is not allowing me to display the images on the page correctly. enter image description here

OfSorts
  • 196
  • 1
  • 1
  • 15

1 Answers1

1

You can use boto3 config to upload your images and that will not add http://localhost:8000/plants in your base url of image.

from storages.backends.s3boto import S3BotoStorage

class PublicMediaStorage(S3Boto3Storage):
    location = "media"
    default_acl = "public-read"
    file_overwrite = False
    custom_domain = False

and use the above class in your model using storage params of django model

class PlantImage(models.Model):
    plant = models.ForeignKey(Plant, default=None, on_delete=models.CASCADE, related_name="plant_images")
    images = models.ImageField(storage=PublicMediaStorage() ,upload_to = 'plant_images/')
 
    def __str__(self):
        return self.plant.name
Divya Prakash
  • 898
  • 1
  • 6
  • 14
  • Thank you! Could you please explain a little more about how that works or share documentation on it? I added the PublicMediaStorage class as you mentioned and I updated my PlantImage class as you showed. However, I did have to modify the import to be `from storages.backends.s3boto3 import S3Boto3Storage`. Now the URL is displaying correctly and images work. However, now I am not able to upload new images and I receive the error of `An error occurred (AccessControlListNotSupported) when calling the PutObject operation: The bucket does not allow ACLs`. – OfSorts Jul 23 '22 at 00:45
  • I updated by bucket permissions to allow get and put ACLs, but no luck. :( I'll keep trying other things, but if I could understand your solution more it may help me. – OfSorts Jul 23 '22 at 00:46
  • I was able to resolve the issue! I (kind of) followed this SO answer: https://stackoverflow.com/questions/36272286/getting-access-denied-when-calling-the-putobject-operation-with-bucket-level-per. I used the s3 policy generator tool to add the policies "s3:PutObject", "s3:PutObjectAcl", "s3:GetObject", "s3:GetObjectAcl", "s3:DeleteObject", this fixed my issue. Although, I still don't understand why the answer here from Divya worked. When I created a successful bucket for static files, I didn't need to use that boto class. – OfSorts Jul 23 '22 at 13:57