How to make the ImageField optional in django rest framework API - django

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)

Related

Django Rest Framework Serializer PrimaryKeyRelatedField pass in uuid list

So, I am trying to make a social media application and users can tag other users in their post.
class Post(models.Model):
author = models.ForeignKey(User, related_name='posts', on_delete=models.CASCADE)
caption = models.CharField(max_length=100, null=True, blank=True)
id = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)
date_created = models.DateTimeField(auto_now_add=True)
date_edited = models.DateTimeField(auto_now=True)
allow_comments = models.BooleanField(default=True)
archived = models.BooleanField(default=False)
media_file_url = models.FileField(upload_to='videos/', validators=[validate_file_extension])
tags = TaggableManager(blank=True, through=TaggedHashtag)
mentioned_users = models.ManyToManyField(User, blank=True, through='MentionedUser')
class MentionedUser(models.Model):
id = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)
date_created = models.DateTimeField(auto_now_add=True)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
tagged_user = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
unique_together = (('post', 'tagged_user'))
ordering = ['date_created']
For serializing the uuids for the mentioned_users, I followed these instructions. Everything works fine, I can submit one uuid correctly like this:
But when I try to submit more than one uuids, I get this error:
Here is my serializer:
class PostSerializer(TaggitSerializer, serializers.ModelSerializer):
tags = TagListSerializerField()
mentioned_users = serializers.PrimaryKeyRelatedField(many=True, allow_null=True, queryset=User.objects.all(), pk_field=serializers.UUIDField(format='hex'))
id = serializers.CharField(read_only=True)
class Meta:
model = Post
fields = [
'caption',
'allow_comments',
'media_file_url',
'id',
'tags',
'mentioned_users',
]
What am I doing wrong here? What is the correct way to submit list of uuids? Also, is the present method I am using efficient, any form of help would be appreciated. Thank You.
Also, I tried adding double quotes for the uuids and also, tried to use '[]' box brackets, but it did not solve my problem.
PS - When I post data to this API from a mobile app, how will I submit it, will it be the same way here or will it be different?
After trying to use ListField(), a drf serializer, and it having its own cannot be empty list error, I took inspiration from TagListSerializer() from django-taggit, and made my own custom serializer, I can upload strings, even empty list, which basically means I don't want to tag anyone. However, I do have to check if the string is valid uuid.
Here is the code for the serializer
from rest_framework import serializer
import json
from django.utils.translation import gettext_lazy
class MentionedUsersIdSerializerField(serializers.Field):
child = serializers.CharField()
initial = []
default_error_messages = {
"not_a_list": gettext_lazy(
'Expected a list of items but got type "{input_type}".'
),
"invalid_json": gettext_lazy(
"Invalid json list. A tag list submitted in string"
" form must be valid json."
),
"not_a_str": gettext_lazy("All list items must be of string type."),
}
order_by = None
def __init__(self, **kwargs):
pretty_print = kwargs.pop("pretty_print", True)
style = kwargs.pop("style", {})
kwargs["style"] = {"base_template": "textarea.html"}
kwargs["style"].update(style)
super().__init__(**kwargs)
self.pretty_print = pretty_print
def to_internal_value(self, value):
if isinstance(value, str):
if not value:
value = "[]"
try:
value = json.loads(value)
except ValueError:
self.fail("invalid_json")
if not isinstance(value, list):
self.fail("not_a_list", input_type=type(value).__name__)
for s in value:
if not isinstance(s, str):
self.fail("not_a_str")
self.child.run_validation(s)
return value
def to_representation(self, value):
if not isinstance(value, list):
if not isinstance(value, list):
if self.order_by:
ids = value.all().order_by(*self.order_by)
else:
ids = value.all()
value = [id.id for id in ids]
value = list(value, pretty_print=self.pretty_print)
return value
I hope this helps someone trying to submit a list of strings to the serializer, where the list can be empty as well.

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

django form not saving despite success message

I have been trying to teach myself something new this week by following various tutorials on Google APIs. I am hitting a wall on the form saving bit, so helping you can help.
Basically, I am trying to offer user the possibility to enter their first line of address for Google to then submit suggestions of addresses.
This first bit works (when I enter the first line of an address, I get generated a list of suggestions).
However, when I am trying to save the form, this doesn't seem to be saved anywhere. And I am not getting any error message either (if anything I am getting the a "success message" I am supposed to receive when the form is successfully submit).
I thought there might be a field that is not getting being populated in the html. So I tried to empty one and tried to save. But on this occasion I received an error message, saying the field is missing. Which makes me thing this hasnt anything to do with the fields in the form but probably how I save my form in the views.py.
I must admit I am way out of my comfort zone here (this ajax thing proved to be difficult to make work, and all I had to do was to follow a tutorial..), so I wouldnt be surprised I am not capturing all the code needed for you to help. If so let me know what you need to see.
Views.py
def is_ajax(request):
return request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
def profile(request):
result = "Error"
message = "There was an error, please try again"
form = profileform(instance = request.user)
Profile.objects.get_or_create(user=request.user)
if is_ajax(request=request):
form = profileform(data = request.POST, instance = request.user)
if form.is_valid():
obj = form.save()
obj.has_profile = True
obj.save()
result = "Success"
message = "Your profile has been updated"
else:
message = FormErrors(form)
data = {'result': result, 'message': message}
return JsonResponse(data)
else:
context = {'form': form}
context['google_api_key'] = settings.GOOGLE_API_KEY
context['base_country'] = settings.BASE_COUNTRY
return render(request, 'main/profile.html', context)
Froms.py
from .models import Profile
class ProfileForm(forms.ModelForm):
address = forms.CharField(max_length=100, required=True, widget = forms.HiddenInput())
town = forms.CharField(max_length=100, required=True, widget = forms.HiddenInput())
county = forms.CharField(max_length=100, required=True, widget = forms.HiddenInput())
post_code = forms.CharField(max_length=8, required=True, widget = forms.HiddenInput())
country = forms.CharField(max_length=40, required=True, widget = forms.HiddenInput())
longitude = forms.CharField(max_length=50, required=True, widget = forms.HiddenInput())
latitude = forms.CharField(max_length=50, required=True, widget = forms.HiddenInput())
class Meta:
model = Profile
fields = ['address','town','county','post_code','country','longitude','latitude']
Models.py
class Profile(models.Model):
user = models.OneToOneField(User, null=True, on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
address = models.CharField(verbose_name="Address",max_length=100, null=True, blank=True)
town = models.CharField(verbose_name="Town",max_length=100, null=True, blank=True)
county = models.CharField(verbose_name="County",max_length=100, null=True, blank=True)
post_code = models.CharField(verbose_name="Post Code",max_length=8, null=True, blank=True)
country = models.CharField(verbose_name="Country",max_length=100, null=True, blank=True)
longitude = models.CharField(verbose_name="Longitude",max_length=50, null=True, blank=True)
latitude = models.CharField(verbose_name="Latitude",max_length=50, null=True, blank=True)
captcha_score = models.FloatField(default = 0.0)
has_profile = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
def __str__(self):
return str(self.user)
Interesting that you're getting to your success message. Your issue might be that you're passing a user instance to a form that expects a profile model (not sure why this doesn't throw an error though):
# capture the profile that is created with get_or_create
profile, created = Profile.objects.get_or_create(user=request.user)
if is_ajax(request=request):
form = profileform(data = request.POST, instance=profile) # change instance
Instead of
else:
message = FormErrors(form)
Try this
else:
message = form.errors

corrupted files uploading csv and zip files via postman toward django rest api

Hi, I'm using django 4.0.6 (python 3.8) with djangorestframework
3.13.1 on windows. I'm testing on localhost my app with postman and postman agent up to date.
The main goal is to send 3 csv file, 1 zip file end 2 textfield
information
The authorization JWT is working fine.
The shortest csv is uploaded correctly
If I upload a long csv or zip files they result in corrupted files.
Only short csv files are correctly uploaded,
long csv files result in corrupted lines on the lower part of
the text with this sort of characters: Û™eÞqHÂÔpŠl°‹<û
yj­Ïª›kÃx›Ûr-¥x¡S¼à2SÕkÛår'mÒÕµd5ÿ¶Vê0#1 ̦Záë1§ŠIÇaÎ
“’ÏÛ€t»vRoT"·‡Qf„¾´é-Oa)]ЧK‹5C¤sWB0),3 Zž—2¸Ñóo«jŸH“
I can't figure out how to fix it, reading other posts I couldn't find
a solution that fit with this situation.
Thank You for any suggestion!
Here my model:
class FileUploader(models.Model):
id_file = models.AutoField('id_file', primary_key=True)
Campaign_Name = models.CharField('Campaign_Name', max_length=255)
ViewpointSurvey = models.FileField('ViewpointSurvey',upload_to=path_and_rename_base,max_length=255,blank=False,null=False,db_column='ViewpointSurvey', name='ViewpointSurvey')
ProjectSurvey = models.FileField('ProjectSurvey', upload_to=path_and_rename_base, max_length=255, blank=False, null=False,
db_column='ProjectSurvey', name='ProjectSurvey')
Trajectories = models.FileField('Trajectories', upload_to=path_and_rename_base, max_length=255, blank=False, null=False,
db_column='Trajectories', name='Trajectories')
screenshots = models.FileField('screenshots', upload_to=path_and_rename_base, max_length=255, blank=False,
null=False,
db_column='screenshots', name='screenshots')
timestamp=models.DateTimeField('timestamp',auto_now_add=True,db_column='timestamp', name='timestamp')
id_project=models.CharField('id_project', max_length=255)
class Meta:
db_table = "file_uploader"
verbose_name_plural = 'file_uploader'
verbose_name = "file_uploader"
Here the view:
class CSVUploadAPI(GenericAPIView):
parser_classes = [MultiPartParser]
serializer_class = UploadSerializer
def put(self, request):
data_collect = request.data
serializer = UploadSerializer(data=data_collect)
if serializer.is_valid():
serializer.save()
return Response('Done')
else:
return Response(UploadSerializer.errors,status=status.HTTP_400_BAD_REQUEST)
Here the serializer:
class UploadSerializer(serializers.ModelSerializer):
class Meta:
model= FileUploader
fields = ('id_project','ProjectSurvey','Trajectories','ViewpointSurvey','screenshots','Campaign_Name')
Here the path_and_rename_base function:
def path_and_rename_base(instance, filename):
upload_to = 'files/'
ext = filename.split('.')[-1]
# set filename as random string
filename = '{}.{}'.format(uuid.uuid4(), ext)
return os.path.join(upload_to, filename)
Here the postman headers
postman headers
Here the postman body
postman body
Here the file list with sizes
Highlighted file is the only one uploaded correctly

Django, want to upload either image (ImageField) or file (FileField)

I have a form in my html page, that prompts user to upload File or Image to the server. I want to be able to upload ether file or image. Let's say if user choose file, image should be null, and vice verso. Right now I can only upload both of them, without error. But If I choose to upload only one of them (let's say I choose image) I will get an error:
"Key 'attachment' not found in <MultiValueDict: {u'image': [<InMemoryUploadedFile: police.jpg (image/jpeg)>]}>"
models.py:
#Description of the file
class FileDescription(models.Model):
TYPE_CHOICES = (
('homework', 'Homework'),
('class', 'Class Papers'),
('random', 'Random Papers')
)
subject = models.ForeignKey('Subjects', null=True, blank=True)
subject_name = models.CharField(max_length=100, unique=False)
category = models.CharField(max_length=100, unique=False, blank=True, null=True)
file_type = models.CharField(max_length=100, choices=TYPE_CHOICES, unique=False)
file_uploaded_by = models.CharField(max_length=100, unique=False)
file_name = models.CharField(max_length=100, unique=False)
file_description = models.TextField(unique=False, blank=True, null=True)
file_creation_time = models.DateTimeField(editable=False)
file_modified_time = models.DateTimeField()
attachment = models.FileField(upload_to='files', blank=True, null=True, max_length=255)
image = models.ImageField(upload_to='files', blank=True, null=True, max_length=255)
def __unicode__(self):
return u'%s' % (self.file_name)
def get_fields(self):
return [(field, field.value_to_string(self)) for field in FileDescription._meta.fields]
def filename(self):
return os.path.basename(self.image.name)
def category_update(self):
category = self.file_name
return category
def save(self, *args, **kwargs):
if self.category is None:
self.category = FileDescription.category_update(self)
for field in self._meta.fields:
if field.name == 'image' or field.name == 'attachment':
field.upload_to = 'files/%s/%s/' % (self.file_uploaded_by, self.file_type)
if not self.id:
self.file_creation_time = datetime.now()
self.file_modified_time = datetime.now()
super(FileDescription, self).save(*args, **kwargs)
forms.py
class ContentForm(forms.ModelForm):
file_name =forms.CharField(max_length=255, widget=forms.TextInput(attrs={'size':20}))
file_description = forms.CharField(widget=forms.Textarea(attrs={'rows':4, 'cols':25}))
class Meta:
model = FileDescription
exclude = ('subject',
'subject_name',
'file_uploaded_by',
'file_creation_time',
'file_modified_time',
'vote')
def clean_file_name(self):
name = self.cleaned_data['file_name']
# check the length of the file name
if len(name) < 2:
raise forms.ValidationError('File name is too short')
# check if file with same name is already exists
if FileDescription.objects.filter(file_name = name).exists():
raise forms.ValidationError('File with this name already exists')
else:
return name
views.py
if request.method == "POST":
if "upload-b" in request.POST:
form = ContentForm(request.POST, request.FILES, instance=subject_id)
if form.is_valid(): # need to add some clean functions
# handle_uploaded_file(request.FILES['attachment'],
# request.user.username,
# request.POST['file_type'])
form.save()
up_f = FileDescription.objects.get_or_create(
subject=subject_id,
subject_name=subject_name,
category = request.POST['category'],
file_type=request.POST['file_type'],
file_uploaded_by = username,
file_name=form.cleaned_data['file_name'],
file_description=request.POST['file_description'],
image = request.FILES['image'],
attachment = request.FILES['attachment'],
)
return HttpResponseRedirect(".")
Let's say if user choose file, image should be null, and vice verso.
You could:
make an SQL constraint,
override model.save() to fail if either file or image is blank,
define ContentForm.clean() to raise a ValidationError if either file or image is blank, see Cleaning and validating fields that depend on each other.
Also be careful that up_f will be a tuple in:
up_f = FileDescription.objects.get_or_create(
I've had the same problem. For some reason the key value dict only takes one key pair values. save the the attachment like so
attachment=form.data['attachment']
as opposed to
attachment=request.FILES['attachment']
it should run, but curious if it will save as a file.
i know this question is old but had a difficult time with this same issue
Create radio buttons for the user to chose what he/she want to upload and use only one FileField attribute in the model. You can convert the other field to BooleanField or CharField to indicate what the user selected.