django-rest-framework and uploading images - django

I have an image field using django-rest-framework how to I handle the uploading of images over the API?
Is there any examples?
models.py
image = models.ImageField(
upload_to="profiles",
height_field="height",
width_field="width",
null=True,
blank=True,
editable=True,
help_text="Profile Picture",
verbose_name="Profile Picture"
)
height = models.PositiveIntegerField(null=True, blank=True, editable=False)
width = models.PositiveIntegerField(null=True, blank=True, editable=False)

Finally I am able to upload image using Django.
Here is my working code
views.py
class FileUploadView(APIView):
# parser_classes = (MultiPartParser, FormParser, )
parser_classes = (FileUploadParser, )
# media_type = 'multipart/form-data'
# format = 'jpg'
def post(self, request, format='jpg'):
up_file = request.FILES['file']
destination = open('/Users/Username/' + up_file.name, 'wb+')
for chunk in up_file.chunks():
destination.write(chunk)
destination.close()
# ...
# do some stuff with uploaded file
# ...
return Response(up_file.name, status.HTTP_201_CREATED)
urls.py
urlpatterns = patterns('',
url(r'^imageUpload', views.FileUploadView.as_view())
curl request to upload
curl -X POST -S -H -u "admin:password" -F "file=#img.jpg;type=image/jpg" 127.0.0.1:8000/resourceurl/imageUpload

The image field doesn't store the image but rather a link to it. Do you have MEDIA_URL set in settings? eg MEDIA_URL = '/media/' If you're running on localhost you should find the image at localhost/media/profiles/image_name.png

Related

Django Large File Serving

I am trying to make an app like youtube, where I need to serve huge number of files and each Video file size can be large [ 50MB or 1GB, or 2GB or more]. I am using SQLite DataBase. How can I serve these files in an efficient way?
models.py
class VideoContent(models.Model):
contenttitle = models.CharField(max_length=300, blank=False)
file = models.FileField(blank=False, verbose_name='Your Video Content',
validators=[FileExtensionValidator(
allowed_extensions=['MOV', 'avi', 'mp4', 'webm', 'mkv']
)])
uploaded = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self) -> str:
return self.contenttitle.title()
** views.py **
class VideoContents(ListView):
template_name = 'contents/index.html'
context_object_name = 'videos'
ordering = '-uploaded'
model = VideoContent
# def get_queryset(self):
# return VideoContent.objects.all()

Detected path traversal attempt - Django/Heroku(Bucketeer)

I'm getting this error when trying to upload using FileField. I'm using Bucketeer on Heroku to upload to an AWS bucket. I've seen a few threads on this issue but haven't been able to figure it out.
The file upload view:
class UploadTicketAttachment(APIView):
permission_classes = []
parser_classes = (MultiPartParser, FormParser)
def post(self, request, format=None):
user = request.user
serializer = AttachmentSerialiazer(data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.validated_data['uploaded_by'] = user
serializer.save()
return Response(serializer.data['id'])
else:
return Response(f'{serializer.errors}, attachment upload failed')
The model:
class Attachment(models.Model):
file = models.FileField(upload_to="/ticket_attachments", blank=True, null=True)
created_on = models.CharField(max_length=20, null=True)
uploaded_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, related_name="uploaded_by")
parent_ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, null=True, related_name="attachment")
def __str__(self):
return self.file.name
For the settings/bucketeer configuration I followed which uses django-storages:
https://dev.to/heroku/properly-managing-django-media-static-files-on-heroku-o2l
I don't think the issue is on that end since I set it up the exact same way in another project and it works fine with the only difference being that the other project uses ImageField instead of FileField.
Django version is 4.0.2. Any ideas? Thanks

Why file extension validator is not working properly in django?

I am trying to add a file extension validator in the Filefield of my model.
But when I am adding a different extension file through my serializer it's adding the extensions I didn't put in my validators.py
Here is the code so far
# validators.py
def validate_file_extension(value):
import os
from django.core.exceptions import ValidationError
ext = os.path.splitext(value.name)[1] # [0] returns path+filename
valid_extensions = ["png", "jpg", "jpeg", # for images
"pdf", "doc", "docx", "txt", # for documents
"mp3", "aac", "m4a", "mp4", "ogg"] # for audios
if not ext.lower() in valid_extensions:
raise ValidationError('Unsupported file extension.')
#models.py
class QuestionFile(models.Model):
question = models.ForeignKey(
Question, on_delete=models.CASCADE, related_name='files', null=True, blank=True)
FILE_TYPE = (
('NONE', 'NONE'),
('IMAGE', 'IMAGE'),
('DOCUMENT', 'DOCUMENT'),
('AUDIO', 'AUDIO'),
)
file_type = models.CharField(
choices=FILE_TYPE, max_length=50, null=True, blank=True)
file = models.FileField(
'files', upload_to=path_and_rename, max_length=500, null=True, blank=True, validators=[validate_file_extension])
def __str__(self):
return str(self.question)
and here is the output in my serializer for this model
"id": ..,
"file": "<filepath>/c-f479b7453519484b827dbc0051bd9a64.html",
"file_type": ..,
"question": ..
As it's visible, though .html extension is not added in the validators.py still it's uploading.
Did I make any mistakes in my code?
I am unable to figure out that.
If any other information is needed let me know, please.
Thanks
Simple error, Your list should be like this:
valid_extensions = ['.jpg', '.png', '.mp3']
and when you use:
import os
text = "/path/to/file/file.jpg"
name = os.path.splitext(text)
then
if name[1].lower() in valid_extensions:
pass

How to make the ImageField optional in django rest framework API

I'm pretty tired finding solution to make the imagefield optional in django rest framework API. I tried each and every solution I found in stack overflow but nothing seems to be working fine. I know there are fewer posts related to same query but I didn't find the solution in that.
Let me explain you what is my requirements.
Here is my User model with some basic info fields including an Image field.
models.py
class User(models.Model):
firstname = models.CharField(max_length=100, validators=[firstname_check])
lastname = models.CharField(max_length=100, blank=True, null=True, validators=[lastname_check])
username = models.CharField(max_length=100, unique=True, blank=True, null=True, validators=[username_check])
email = models.EmailField(max_length=100, unique=True)
password = models.CharField(max_length=50, blank=True, null=True, validators=[password_check])
mobnum = models.CharField(max_length=50, blank=True, null=True, validators=[mobile_num_len])
timestamp = models.DateTimeField(auto_now=True)
gender = models.CharField(max_length=50, blank=True, null=True)
language = models.CharField(max_length=100, blank=True, null=True)
profile_img = models.ImageField(upload_to ='uploads/', blank=True, null=True)
is_active = models.BooleanField(default=False, blank=True, null=True)
serializer.py
from utils import Base64ImageField
class UserMannualSerializer(serializers.ModelSerializer):
profile_img = Base64ImageField(
max_length=None, use_url=True, allow_empty_file=True, required=False
)
class Meta:
model = User
fields = [
'firstname',
'lastname',
'username',
'email',
'password',
'mobnum',
'gender',
'language',
'timestamp'
'profile_img'
]
Here is the Base64ImageField() function which I've written inside the utils.py file. Which will take the base64 string and convert it back and store in the server and that is happening properly. But when I don't upload the image, it throws an error. If I pass the (allow_empty_file=True, required=False) attributes and its values in the Base64ImageField(), even also it doesn't work at all.
utils.py
class Base64ImageField(serializers.ImageField):
"""
A Django REST framework field for handling image-uploads through raw post data.
It uses base64 for encoding and decoding the contents of the file.
Heavily based on
https://github.com/tomchristie/django-rest-framework/pull/1268
Updated for Django REST framework 3.
"""
def to_internal_value(self, data):
# Check if this is a base64 string
if isinstance(data, six.string_types):
# Check if the base64 string is in the "data:" format
if 'data:' in data and ';base64,' in data:
# Break out the header from the base64 content
header, data = data.split(';base64,')
# Try to decode the file. Return validation error if it fails.
try:
decoded_file = base64.b64decode(data)
except TypeError:
self.fail('invalid_image')
# Generate file name:
file_name = str(uuid.uuid4())[:12] # 12 characters are more than enough.
# Get the file name extension:
file_extension = self.get_file_extension(file_name, decoded_file)
complete_file_name = "%s.%s" % (file_name, file_extension, )
data = ContentFile(decoded_file, name=complete_file_name)
return super(Base64ImageField, self).to_internal_value(data)
def get_file_extension(self, file_name, decoded_file):
import imghdr
extension = imghdr.what(file_name, decoded_file)
extension = "jpg" if extension == "jpeg" else extension
return extension
Note: If I give default field in the ImageField, then also it's not working. It's not taking the default image if I don't upload one.
I'm working with Python 3.6, Django 3.1.1, djangorestframework 3.11.1
Please let me know if someone has already been faced this problem and got the solution or any alternative would also help. I would appreciate your help. Thank you.
Edited part
Here is the code from another app, how I'm sending the code from the other django app.
def register_confirm(request):
"""
getting the sign-up required values
dumping the all the parameters as json
using requests library to achieve the register via API
passing the success response to profile.html
"""
std_mobnum = ''
if request.method == "POST":
firstname = request.POST.get("fname")
lastname = request.POST.get("lname")
username = request.POST.get("uname")
email = request.POST.get("email")
password = request.POST.get("pswd")
mobnum = request.POST.get("mobnum")
image = request.POST.get('imagtobase64')
gender = request.POST.get("gender")
language = request.POST.get("language")
params={
'firstname':firstname,
'lastname':lastname,
'username':username,
'email':email,
'password':password,
'mobnum':mobnum,
'profile_img':image,
'gender':gender,
'language':language,
}
print(params)
headers = {'content-type':'application/json'}
response = requests.post("http://127.0.0.1:8000/user-create/", data=json.dumps(params), headers=headers)
user_data = response.json()
print(user_data)
return JsonResponse(user_data)
Note: I'm sending the base64 string of an image using jquery to Api.
This is the code of views.py file where I'm handling the serializer and post request.
views.py
api_view(['POST'])
#parser_classes([MultipartJsonParser, FormParser, JSONParser])
def userCreate(request):
status=''
message = ''
data ={}
try:
mannualSerializer = UserMannualSerializer(data = request.data)
print("serializer started")
if mannualSerializer.is_valid():
print("form validated")
mannualSerializer.save()
print("saved")
status='success'
message = 'User created successfully.'
data= mannualSerializer.data
except Exception as error:
status='failure'
message = error
response = {
"status":status,
"message":message,
"data":data
}
return Response(response)

Django DRF Image store private for user only on S3

I am using DRF for storing user uploaded images to S3 and in S3 i can see that images are public accessible using the URL.
My concern over here is there any best way to secure this images and restrict them for owner of that image only to view it.
I am using Heroku to deploy my DRF API Framework but i see this as security concern for my user who are uploading image files to S3 bucket.
I am trying to isolate user images by their name it self.But still it is public so i can access this images for another user just figure it out there name.
Here is S3 URL for media Images
https://xxx.s3.amazonaws.com/media/persons/niravjoshi/20181218152410.jpg
Here is my settings.py for Django
import os
import pymysql # noqa: 402
pymysql.install_as_MySQLdb()
import dj_database_url
from decouple import config
import django_heroku
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
#SECRET_KEY = 'feufm)u(pvsvb%&_%%*)p_bpa+sv8zt$#_-do5q3(vou-j*d#p'
SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', default=False, cast=bool)
DATABASES = {
'default': dj_database_url.config(
default=config('DATABASE_URL')
)
}
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sites',
#Django Project Apps
'persons',
'rest_framework',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.google',
#'social_django',
]
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/
AWS_REGION = os.environ.get('AWS_REGION', '') # e.g. eu-west-1
AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY', '')
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_KEY', '')
AWS_STORAGE_BUCKET_NAME = os.environ.get('S3_BUCKET', '')
AWS_QUERYSTRING_AUTH = False
AWS_S3_CUSTOM_DOMAIN = os.environ.get("AWS_S3_CUSTOM_DOMAIN", "")
MEDIAFILES_LOCATION = 'media'
DEFAULT_FILE_STORAGE = 'DjangoE2ISAapi.storage_backends.MediaStorage'
MEDIA_URL = "https://%s/%s/" % (AWS_S3_CUSTOM_DOMAIN, MEDIAFILES_LOCATION)
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
AWS_STATIC_LOCATION = 'static'
STATICFILES_STORAGE = 'DjangoE2ISAapi.storage_backends.StaticStorage'
STATIC_URL = "https://%s/%s/" % (AWS_S3_CUSTOM_DOMAIN, AWS_STATIC_LOCATION)
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
django_heroku.settings(locals())
from DjangoE2ISAapi.restconf.main import *
Here is my storage_backends.py
from django.conf import settings
from storages.backends.s3boto3 import S3Boto3Storage
class StaticStorage(S3Boto3Storage):
location = settings.AWS_STATIC_LOCATION
class MediaStorage(S3Boto3Storage):
location = settings.MEDIAFILES_LOCATION
Here is my Person model.py.
from django.core.serializers import serialize
from django.db import models
from django.conf import settings
import json
from django.core.serializers.json import DjangoJSONEncoder
# Create your models here.
def upload_file(instance,filename):
import os
from django.utils.timezone import now
filename_base, filename_ext = os.path.splitext(filename)
return "persons/{user}/{filename}".format(user=instance.UserName, filename=now().strftime("%Y%m%d%H%M%S")+filename_ext.lower())
class PersonQuerySet(models.QuerySet):
def serialize(self):
list_values=list(self.values('UserName','PersonId','PersonName','Person_Image','Person_sex','Person_BDate'))
print (list_values)
return json.dumps(list_values,sort_keys=True,indent=1,cls=DjangoJSONEncoder)
class PersonManager(models.Manager):
def get_queryset(self):
return PersonQuerySet(self.model,using=self._db)
class Person(models.Model):
UserName = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE,)
PersonId = models.AutoField(primary_key=True)
PersonName = models.CharField("person's first name", max_length=30,null=False)
Person_Image = models.ImageField(upload_to=upload_file,null=True, blank=True)
SEX = (('M','Male'),('F','Female'), ('N','None'), )
Person_sex = models.CharField(max_length=1,choices=SEX,null=False)
Person_BDate = models.DateField(null=False)
Person_CDate = models.DateField(null=False,auto_now_add=True)
objects = PersonManager()
def __str__(self):
return str(self.PersonName) or ""
def serialize(self):
data={
'UserName': self.UserName,
'PersonId': self.PersonId,
'PersonName': self.PersonName,
'Person_Image':self.Person_Image,
'Person_sex': self.Person_sex,
'Person_Bdate': self.Person_BDate
}
data = json.dumps(data,sort_keys=True,indent=1,cls=DjangoJSONEncoder)
return data
#property
def owner(self):
return self.UserName
Here is response of Person API View:
The docs for boto's ACLs are here. I suggest just using the private "canned policy" -- since your users don't have S3 accounts anyway, it's by far the simplest idea. Your app will of course have to keep track of which user "owns" which files (which should be a very, very simple Django model!).
In order to enforce users only being able to download through your own application, just pass a small value to the expires_in parameter when generating the URL. Users will only get a valid download link through your application, and that link will be invalidated after their download.
Here is an example of the code used to generate the link for downloading :
#login_required
def download_document(request, file_id):
'''
Request handler to download file
'''
file = Document.objects.get(pk=file_id)
s3 = get_aws_s3_client() #function to create s3 session
download_url = s3.generate_presigned_url(
'get_object',
Params= {'Bucket': file.bucket_name, 'Key': file.key},
ExpiresIn=5, #the url won't be valid after only 5 seconds
)
return redirect(download_url)
You can go further and make the view valid only for the file owner by adding this code :
if file.owner == request.user :
return redirect(download_url)
else :
# render 403.html since access denied.
Edit :
As requested, this solution requires using a specific model to store informations related to each document.
The model will look similar to this :
class Image(models.Model):
customer = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete = models.CASCADE,)
key = models.CharField(max_length=120) #uuid64 will be stored here and used for s3 urls
name = models.CharField(max_length=120, null=True, blank=True)
size = models.FloatField()
human_size = models.CharField(max_length=120, null=True, blank=True)
filetype = models.CharField(max_length=120, null=True, blank=True)
fextension = models.CharField(max_length=30, null=True, blank=True)
bucket_name = models.CharField(max_length=120, null=True, blank=True)
region = models.CharField(max_length=120, null=True, blank=True)
s3link = models.CharField(max_length=170, null=True, blank=True)
timestamp = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
uploaded = models.BooleanField(default=False)
active = models.BooleanField(default=True)
def __str__(self):
return self.name
I can't discuss parts related to serialization since I have never used DRF.
I would add uuid field to every user like.
import uuid
class Person(models.Model):
uuid = models.UUID(default=uuid.uuid4)
....
You can set it as primary key as well instead of AutoField.
and put that unique uuid into URL instead of name in order to look like:
https://xxx.s3.amazonaws.com/media/persons/b3810ec3-dd9d-4f11-a1e1-47835c0058ec/20181218152410.jpg
That image will be still public, but it's impossible to access image if you don't know uuid of particular user.
If you want more secure solution not depending on URL only you need to add some Authentication logic.