segmented image not displayed in flask app - python

I am making an image segmentation app which segments image into 'k' colors
the code for flask app goes like this:
##not pasting standard starting code
app.config['template_path'] = r'C:\Users\Asus\Documents\deep learning\deep_learn\NOTEBOOKS\app\templates'
def image_return():
if request.method == 'POST':
if request.files:
print('address accessed')
file = request.files['img']
k = request.values['k']
print('DONE FILE')
if not file.filename == '':
print('FILENAME EXISTS')
name = secure_filename(file.filename)
print(name)
address = os.path.join(
app.config['template_path'], 'uploads/', name)
file.save(address)
print('DONEEEE')
img = reader(address)
red_image, path = main(img, name, k)
print('image red. and saved to path')
return red_image, path
else:
redirect(request.url)
return flash('something went wrong try again')
# app.route('/transformed', methods=['POST'])
def transform():
red_image, path = image_return()
return render_template('imagetrans.html', address=path)
### ALL THE PRINT STATEMENTS RAN SUCCESSFULLY AND SAVED THE CONVERTED IMAGE TO THE PATH
## THE APPROACH I AM USING IS TO NOT DISPLAY LIVE IMAGE BUT TO SAVE IS FIRSTLY AND THEN DISPLAY IT FROM A SAVED LOCATION
the HTML goes like this:
<body>
<center>
<img src={{address}} alt="display.error" />
</center>
</body>
the python code for generating images works well
def color_reduction(img, k):
# transform the image
print(type(img))
data = np.float32(img).reshape((-1, 3))
# determine the criteria
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 20, 0.001)
# implementing k - means
ret, label, center = cv2.kmeans(
data, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
center = np.uint8(center)
result = center[label.flatten()]
result = result.reshape(img.shape)
return result
template_path = r'C:\Users\Asus\Documents\deep learning\deep_learn\NOTEBOOKS\app\templates\transformed'
def main(img, filename, k=5, title=None):
red_image = color_reduction(img, k=int(k))
path = os.path.join(template_path, f'{filename}_{k}.jpg')
cv2.imwrite(path, red_image)
return red_image, path
the reduced_image is not read in img src even after passing correct variable address
I think I am going wrong at the HTML part!

Have you checked what the value for address is in the html, i.e. if you look at the page source what does the address value render as in your browser?
I think this is probably going wrong since it looks like you are passing an absolute filepath to the html whereas really you should pass a path that is relative to the flask app instance.
i.e. if your flask app is in a folder called my_site then to src you would just pass the path from my_site to the image, i.e. my_site\static\img\img.jpg rather than C:\Users\Documents\projects\my_site\static\img\img.jpg.

Related

Django Imagefield renaming works only every other time

I'm attempting to upload a profile picture to a django model which should always be named pic.jpg. Old pictures are deleted using django_cleanup.
This works every other time. I upload an image and it's saved as pic.jpg, then upload a different one and it's saved as pic_{randomchars}.jpg (i.e pic_wCU5xwv.jpg).
def rename_pic(instance, filename):
return os.path.join("api/media/me/", filename)
pic = models.ImageField(upload_to=rename_pic)
def save(self, *args, **kwargs):
try:
# Opening the uploaded image
img = Image.open(self.pic)
output = BytesIO()
img = img.convert('RGB')
# after modifications, save it to the output
img.save(output, format='JPEG')
output.seek(0)
# Set field to modified picture
self.pic = InMemoryUploadedFile(output, 'ImageField', "pic.jpg",
'image/jpeg', sys.getsizeof(output), None)
except Exception as e:
print(e)
print(self.pic.name) # Always prints pic.jpg
super(MyData, self).save() # Error happens in this line
print(self.pic.name) # Prints api/media/me/pic.jpg and api/media/me/pic_{randomchars}.jpg alternating
The error happens somewhere in the super(MyData, self).save() line, as the file has the correct name before it's called.

New to HTML and Flask. How to let a user submit an image and description without having to sign in?

I am currently designing a steganography web app as a part of my beginner Computer Science summer program. With a very limited understanding of HTML, Flask, and the interconnected languages like SQL and jquery, I've ran into a bit of a roadblock. So far I've looked at a lot of beginner's flask tutorials, but they all have a tendency to focus on creating forums with users and the ability to post.
My vision for the web app is to have a page titled "encrypt" that has two fields required (text/message and an image to put the text in) and a submit button that then runs my python program and gives the user the image with the hidden message. Through a ton of web searches, I've found ways to make a form with two fields and how to upload to a folder.
Somewhere in the middle, however, my web app began to strongly resemble Frankenstein's monster with bits of code taken from every which place stitched into one disfigured and possibly (read probably) incoherent code. Of course, as a result of this, my code fails to do the very thing it was made to do.
My steganography code works well enough (shown to give a better understanding of what I aim to achieve. The larger goal of this is more applicable because it aims to use multiple user-submitted values in order to run a python program and does not involve registered members, a desire that most tutorials neglect):
def packing(s):
'''converts a characters 8 bits into 4 two-bit values and adds them to a
list using a for loop'''
l = []
for i in range(len(s)):
x = ord(s[i])
top2 = (x & 0xC0) >> 6
middletop2 = (x & 0x30) >> 4
lowertop2 = (x & 0xC) >> 2
lower2 = (x & 0x3)
l.extend([top2, middletop2, lowertop2, lower2])
length = len(l)
h1 = (length & 0xC0000000) >> 30
h2 = (length & 0x30000000) >> 28
h3 = (length & 0x0C000000) >> 26
h4 = (length & 0x03000000) >> 24
h5 = (length & 0x00C00000) >> 22
h6 = (length & 0x00300000) >> 20
h7 = (length & 0x000C0000) >> 18
h8 = (length & 0x00030000) >> 16
h9 = (length & 0x0000C000) >> 14
hA = (length & 0x00003000) >> 12
hB = (length & 0x00000C00) >> 10
hC = (length & 0x00000300) >> 8
hD = (length & 0x000000C0) >> 6
hE = (length & 0x00000030) >> 4
hF = (length & 0x0000000C) >> 2
hF1 = (length & 0x00000003)
l = ([h1] + [h2] + [h3] + [h4] + [h5] + [h6] + [h7] + [h8] + [h9] +
[hA] + [hB] + [hC] + [hD] + [hE] + [hF] + [hF1] + l)
return l
def bitsIntoImage(pic, l):
'''wipes the last two bits of each R, G and B value for every pixel
nevessary to import the message. Then writes the rest of the image onto the
new image to return a complete image.'''
pic = Image.open( pic )
draw = ImageDraw.Draw(pic)
(width, height) = pic.size
newPic = Image.new('RGB', (width,height))
drawnewPic = ImageDraw.Draw(newPic)
if len(l) % 3 == 1:
l = l + [0,0]
if len(l) % 3 == 2:
l = l + [0]
redL = l[0::3]
greenL = l[1::3]
blueL = l[2::3]
for y in xrange(height):
for x in xrange(width):
if len(redL) > 0:
openRed = pic.getpixel((x,y))[0] &~ 0x3
openGreen = pic.getpixel((x,y))[1] &~ 0x3
openBlue = pic.getpixel((x,y))[2] &~ 0x3
codedRed = openRed | redL[0]
codedGreen = openGreen | greenL[0]
codedBlue = openBlue | blueL[0]
redL = redL[1:]
greenL = greenL[1:]
blueL = blueL[1:]
drawnewPic.point([x,y], (codedRed, codedGreen, codedBlue))
else:
(R, G, B) = pic.getpixel((x,y))
drawnewPic.point([x,y], (R, G, B))
return newPic
def step1(pic, s):
'''pic = picture, s = message/string. Turns the string into a list of
double-bit information, then imports that string of information into the image'''
l = packing(s)
picture = bitsIntoImage(pic, l)
return picture
But I'm pretty sure that my actual code fails to hold the user submitted image and message in such a way that my steganography program can actually use the values:
import os
import tempfile
import re
from flask.ext.wtf import Form
from wtforms import StringField, TextAreaField, FileField, validators
from wtforms.validators import DataRequired
from flask_wtf.file import FileAllowed, FileRequired
from werkzeug import secure_filename
from PIL import Image, ImageDraw
from steganography import packing, bitsIntoImage, step1, MinaB, unpacking, step2
UPLOAD_FOLDER = '/Users/AustinMossEnnis/practice/uploads/'
ALLOWED_EXTENSIONS = set(['png'])
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
app = Flask(__name__)
app.config['SECRET_KEY'] = 'string'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
class EncryptForm(Form):
image = FileField(u'Upload your image:', validators = ['png'])
message = TextAreaField(u'Enter your message:', [validators.Length(min=1)])
def validate_image(form, field):
if field.data:
field.data = re.sub(r'[^a-z0-9_.-]', '_', field.data)
#app.route('/')
def index():
return render_template('index.html')
#app.route('/encrypt', methods=['GET', 'POST'])
def encrypt():
form = EncryptForm(request.form)
if request.method == "POST" and 'docs' in request.files:
#I had tried using "if form.image.data and form.validate():" to no avail
image_data = request.FILES[form.image.name].read()
step1(image_data, form.message())
file.save(os.path.join(app.config['UPLOAD_FOLDER'], newPic))
redirect(url_for('uploaded_file',
newPic=filename))
return render_template('encrypt.html',
title='Encrpyt',
form=form)
#app.route('/encrypt/<filename>')
def encrypted_file(filename):
return send_from_directory(app.config['UPLOAD_FOLDER'],
filename)
if __name__ == '__main__':
app.run(
host="0.0.0.0",
port=int("5000"),
debug=True
)
As you can tell from my residual imports, I've tried a ton of things with mixed results. Along the way, I've gotten "error, v # invalid expression", "TypeError: 'str' object is not callable", amongst other error messages.
So, in summary, my question is how I can properly take the values submitted by the user, integrate them into my program, and then give back the product of that program?
Would it be preferable to have a temp folder of some sort? Is it necessary to create databases regardless of whether or not I have users? I've tried a lot but have failed either due to a failure to properly execute the text or as a result of not understanding the code I was trying to execute.
You want the user to provide his own image file, so the file must be uploaded. The first link with the two text fields just passes on two strings from the request form. Unless the server has a file locally with the same file name as passed from the text field (and it shouldn't, since you're trying to upload a file it doesn't have), it can't access the data. The second link you provided has almost 90% of your solution. We'll focus on this and slightly modify it to our needs.
The form block is the only important part in the html file. It creates a file input to upload a file and a submit input to send the form. You can read more about the various types of inputs here. We just need an extra text input for our message.
<form action="upload" method="post" enctype="multipart/form-data">
<input type="file" name="file"><br>
<input type="text" name="message"><br><br>
<input type="submit" value="Upload">
</form>
On the python side of things, we only need to change the upload function. But let's understand what it does first.
def upload():
file = request.files['file']
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return redirect(url_for('uploaded_file', filename=filename))
The request object contains all the data from our submitted form. You can see all its attributes/methods by printing dir(request). request.files holds the data from all uploaded files. By extension, request.values holds the data from text fields. Both of these are special dictionary structures, but just like a normal dictionary, you can access a value with its key. Since in the html file we called our inputs "file" and "message", that's how we access them.
The uploaded file, accessed with request.files['file'], is stored in another special data structure called FileStorage. From this class we can access the name of the file we uploaded with the filename attribute and we can save the actual data to our server with save() method. The os.path.join function just concatenates the upload folder path with the file name so we can define the destination of the saved file on our server.
There's one new thing we're interested in this data structure and that's the stream attribute. This returns the actual binary data of our uploaded file as a buffered stream. We can use this along with our message and pass them on to your steganography function. All in all, our modified code should look like this.
def upload():
file = request.files['file']
text = request.values['message']
if file and allowed_file(file.filename) and text:
# =============================================================
# We do our embedding here and return the modified image stream
file.stream = embed(file.stream.read(), text)
# =============================================================
fname = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], fname))
return redirect(url_for('uploaded_file', filename=fname))
The last modification has to do with your embedding function. When you feed a file name or byte stream to Image.open, you create an Image object. In order to create a file stream out of that, you would normally save the data to a file and load it again with something like open('filename', 'rb').read(). But since we want to do all of this in memory, we can use StringIO. Basically, your embedding function must look something like this.
from io import BytesIO
from StringIO import StringIO
def embed(stream, text):
image = Image.open(stream)
bits = packing(text)
# pixel modification goes here...
output = StringIO.StringIO()
image.save(output, format='png')
contents = BytesIO(output.getvalue())
output.close()
return contents
While this is optional, I'm offering some suggestions to improve the readability of your embedding code. You have a few repetitions which can be streamlined and you can avoid creating a second Image object if you're going to discard the first one anyway. You should also not go through all of the pixels if you have only a few bits to embed. This can a serious impact with big images.
def packing(s):
'''Convert a message into 2-bit groups with a size header'''
# Store the size of the message bit groups first
length = len(s) * 4
bit_groups = [(length >> i) & 0x03 for i in xrange(30, -1, -2)]
for char in s:
byte = ord(char)
byte_decomposition = [(byte >> i) & 0x03 for i in xrange(6, -1, -2)]
bit_groups.extend(byte_decomposition)
return bit_groups
def embed(stream, text):
'''Embed a message in the RGB pixels of an image in groups of 2 bits.'''
image = Image.open(stream)
bits = packing(text)
mask = ~0x03
max_width = image.size[0]
height = 0
width = 0
color = 0
for b in bits:
if color == 0:
pixel = list(image.getpixel((width, height)))
pixel[color] = (pixel[color] & mask) | b
color += 1
if color == 3:
image.putpixel((width, height), tuple(pixel))
color = 0
width += 1
if width == max_width:
width = 0
height += 1
# Convert the Image object to a ByteIO stream as shown above and return
return contents

Is there a cleaner way to rotate smartphone images uploaded via flask before pushing to S3?

I'm building a webapp that takes uploaded images, stores them on Amazon S3 and then stores the URL in a SQLite database. Unfortunately, EXIF tags cause images that were taken via a smartphone to appear rotated (since they are landscape images w/ EXIF orientation tags).
Currently, my environment grabs the file from the POST data, saves it to my static files folder, rotates image (if needed) with PIL, pushes to S3 and finally deletes the local copy. Here is a little of the code involved:
from PIL import Image
import boto
from boto.s3.connection import S3Connection
from boto.s3.key import Key
def fix_orientation(filename):
img = Image.open(filename)
if hasattr(img, '_getexif'):
exifdata = img._getexif()
try:
orientation = exifdata.get(274)
except:
# There was no EXIF Orientation Data
orientation = 1
else:
orientation = 1
if orientation is 1: # Horizontal (normal)
pass
elif orientation is 2: # Mirrored horizontal
img = img.transpose(Image.FLIP_LEFT_RIGHT)
elif orientation is 3: # Rotated 180
img = img.rotate(180)
elif orientation is 4: # Mirrored vertical
img = img.rotate(180).transpose(Image.FLIP_LEFT_RIGHT)
elif orientation is 5: # Mirrored horizontal then rotated 90 CCW
img = img.rotate(-90).transpose(Image.FLIP_LEFT_RIGHT)
elif orientation is 6: # Rotated 90 CCW
img = img.rotate(-90)
elif orientation is 7: # Mirrored horizontal then rotated 90 CW
img = img.rotate(90).transpose(Image.FLIP_LEFT_RIGHT)
elif orientation is 8: # Rotated 90 CW
img = img.rotate(90)
#save the result and overwrite the originally uploaded image
img.save(filename)
def push_to_s3(**kwargs):
try:
conn = S3Connection(app.config["S3_KEY"], app.config["S3_SECRET"])
buckets = [bucket.name for bucket in conn.get_all_buckets()]
bucket = conn.get_bucket(app.config["S3_BUCKET"])
k = Key(bucket)
k.key = app.config["S3_UPLOAD_DIR"] + kwargs.get("filename")
k.set_contents_from_filename(kwargs.get("photo"))
k.make_public()
return k
except Exception, e:
abort(500)
Here is handling the POST data
# Retrieving Form POST Data
fi = request.files.get("file")
#print "Storing and Rotating File (if needed)"
f = photos.save(fi)
path = photos.path(f)
fix_orientation(path)
#print "Uploading to S3"
img = push_to_s3(photo=path, filename=filename)
#print "Deleting Local Version"
os.remove(path)
The above solution works on Heroku's servers, but it just seems very duct tape'd together of a solution. Is there are cleaner way to do what I'm doing. That is, take a uploaded file, rotate it from memory and then push to S3?
I'm also using Flask-Uploads to handle storage of the upload images.
For what it is worth, Pillow supports a number of other inputs than a file name - including bytearray, buffer, and file-like object. The third is most probably what you are looking for, as anything loaded out of request.files is just a FileStorage file-like object. That simplifies the load-and-transform code to:
def fix_orientation(file_like_object):
img = Image.open(filename)
# ... snip ...
data = BytesIO()
img.save(data)
return data
Since we are going to be passing around data without using the filesystem very much, we can also switch to using boto.s3.key.Key's set_contents_from_file method instead of set_contents_from_filename:
def push_to_s3(photo, filename):
# ... snip ...
k.set_contents_from_file(photo, rewind=True)
# ... etc. ...
That simplifies the resulting implementation to:
# Retrieving Form POST Data
fi = request.files.get("file")
# print "Rotating File (if needed)"
fi = fix_orientation(fi)
# print "Uploading to S3"
push_to_s3(photo=fi, filename=filename)

How to change the format of an Image upload field in Web2py?

Suppose i have an Upload field for an Image (like - Profile Picture) for a record , so my questions is that how will i be able to change the format of that picture ?
I would also love to use the PIL or PythonMagick API , but how would i do that in Web2py ??
Let's suppose you have a profile table, and an image table
Then, you have a controller to edit a profile image.
By "change the format of the picture", I suppose you want to resize the image, of create a thumbnail...
Here is an example using PIL :
def edit_image():
"""
Edit a profile image, creates a thumb...
"""
thumb=""
profile = db.profile(request.vars.profile_id)
image = db(db.image.id==profile.image).select().first()
if image:
form = SQLFORM(db.image, image, deletable=True, showid=False)
thumb = image.thumb
else:
form = SQLFORM(db.image)
if form.accepts(request.vars, session):
response.flash = T('form accepted')
#resize the original image to a better size and create a thumbnail
__makeThumbnail(db.image,form.vars.id,(800,800),(260,260))
redirect(URL('images'))
elif form.errors:
response.flash = T('form has errors')
return dict(form=form,thumb=thumb)
Here is the code of __makeThumbnail
def __makeThumbnail(dbtable,ImageID,image_size=(600,600), thumbnail_size=(260,260)):
try:
thisImage=db(dbtable.id==ImageID).select()[0]
from PIL import Image
except: return
full_path = path.join(request.folder,'static','images', thisImage.file)
im = Image.open(full_path)
im.thumbnail(image_size,Image.ANTIALIAS)
im.save(full_path)
thumbName='thumb.%s' % (thisImage.file)
full_path = path.join(request.folder,'static','images', 'thumbs',thumbName)
try:
im.thumbnail(thumbnail_size,Image.ANTIALIAS)
except:
pass
im.save(full_path)
thisImage.update_record(thumb=thumbName)
return

Django: Image Resize and Upload with PIL, Amazon S3 and Boto

I'm trying to figure out the best way to take a user uploaded image, resize it, and store the original image as well as the resized image on Amazon S3.
I'm running Django 1.5, using PIL to resize the image, and using Boto to handle uploading the image file to S3. Right now I've got it to work by uploading the original image to S3, using PIL to open the image using the S3 path and resize it, and then saving the resized version to S3, however this doesn't seem to be the most efficient way to do this.
I'm wondering if there's a way to resize the image before uploading to S3 using the user-uploaded image itself (been having trouble getting PIL to open the image file itself), and whether this would be faster than the way I've set things up now. I can't seem to find an answer to this, either in the PIL documentation or anywhere else. I should mention that I don't want to just use a third party app to handle this, as part of my goal is to learn and understand fundamentally what is going on.
Is there a more efficient way to do this than what I've currently set up? A general explanation of what is happening at each step and why it makes the most sense to set things up that way would be ideal.
I should also mention that it seems to take much longer to upload the image to S3 than when I was just storing the image on my server. Is there a normal lag when uploading to S3 or is there potentially something in how things are set up that could be slowing down the S3 uploads?
I have an architecture consisting of a Django + Tastypie in Heroku and the image wharehouse in S3. What I do when a user uploads a photo from the frontend (written in JS), is resize the photo to a certain size (600 x 600 max size) always mantaining the aspect ratio. I'll paste the code to do this (it works).
views.py:
class UploadView(FormView):
form_class = OriginalForm
def form_valid(self, form):
original = form.save()
if original.image_width > 280 and original.image_height > 281:
if original.image_width > 600 or original.image_height > 600:
original.resize((600, 600))
if not original.image:
return self.success(self.request, form, None, errors = 'Error while uploading the image')
original.save()
up = UserProfile.objects.get(user = request.user.pk)
#Save the images to s3
s3 = S3Custom()
new_image = s3.upload_file(original.image.path, 'avatar')
#Save the s3 image path, as string, in the user profile
up.avatar = new_image
up.save
else:
return self.success(self.request, form, None, errors = 'The image is too small')
return self.success(self.request, form, original)
Here what I do is checking if the image is larger than 280 x 281 (the crop square, in the frontend, has that size), and also check if one of the sides of the image is larger than 600px. If that's the case, I call the (custom) method resize, of my Original class...
models.py:
class Original(models.Model):
def upload_image(self, filename):
return u'avatar/{name}.{ext}'.format(
name = uuid.uuid4().hex,
ext = os.path.splitext(filename)[1].strip('.')
)
def __unicode__(self):
return unicode(self.image)
owner = models.ForeignKey('people.UserProfile')
image = models.ImageField(upload_to = upload_image, width_field = 'image_width', height_field = 'image_height')
image_width = models.PositiveIntegerField(editable = False, default = 0)
image_height = models.PositiveIntegerField(editable = False, default = 0)
def resize(self, size):
if self.image is None or self.image_width is None or self.image_height is None:
print 'Cannot resize None things'
else:
IMG_TYPE = os.path.splitext(self.image.name)[1].strip('.')
if IMG_TYPE == 'jpeg':
PIL_TYPE = 'jpeg'
FILE_EXTENSION = 'jpeg'
elif IMG_TYPE == 'jpg':
PIL_TYPE = 'jpeg'
FILE_EXTENSION = 'jpeg'
elif IMG_TYPE == 'png':
PIL_TYPE = 'png'
FILE_EXTENSION = 'png'
elif IMG_TYPE == 'gif':
PIL_TYPE = 'gif'
FILE_EXTENSION = 'gif'
else:
print 'Not a valid format'
self.image = None
return
#Open the image from the ImageField and save the path
original_path = self.image.path
fp = open(self.image.path, 'rb')
im = Image.open(StringIO(fp.read()))
#Resize the image
im.thumbnail(size, Image.ANTIALIAS)
#Save the image
temp_handle = StringIO()
im.save(temp_handle, PIL_TYPE)
temp_handle.seek(0)
#Save image to a SimpleUploadedFile which can be saved into ImageField
suf = SimpleUploadedFile(os.path.split(self.image.name)[-1], temp_handle.read(), content_type=IMG_TYPE)
#Save SimpleUploadedFile into image field
self.image.save('%s.%s' % (os.path.splitext(suf.name)[0],FILE_EXTENSION), suf, save=False)
#Delete the original image
fp.close()
os.remove(original_path)
#Save other fields
self.image_width = im.size[0]
self.image_height = im.size[1]
return
The last thing you need is a "library" containing custom s3 methods:
class S3Custom(object):
conn = S3Connection(settings.AWS_ACCESS_KEY_ID, settings.AWS_SECRET_ACCESS_KEY)
b = Bucket(conn, settings.AWS_STORAGE_BUCKET_NAME)
k = Key(b)
def upload_file(self, ruta, prefix):
try:
self.k.key = '%s/%s' % (prefix, os.path.split(ruta)[-1])
self.k.set_contents_from_filename(ruta)
self.k.make_public()
except Exception, e:
print e
return '%s%s' % (settings.S3_URL, self.k.key)
You should have AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_STORAGE_BUCKET_NAME, S3_URL in your settings file.

Categories

Resources