Reputation: 1892
I have a model of images, Image
that have foreign keys on different types of articles.
I want to expose this model via a REST interface (built with Django-Rest-Framework) and upload images to it via AJAX calls in Angular 10.
Doing File Uploads in general works so far, as I was able to follow this guide here successfully (EDIT: This is incorrect as I have come to realize. It did create files, but those weren't actual images, they were in fact unreadable).
It does however somehow not work with my ImageModel and ImageSerializer. When I fire my AJAX call at the moment, currently I get a HTTP 500 response on the frontend and this error in the backend in Django:
File "/home/isofruit/.virtualenvs/AldruneWiki-xa3nBChR/lib/python3.6/site-packages/rest_framework/serializers.py", line 207, in save
'create() did not return an object instance.'
This is a console log of the content of the FormData object I send via AJAX call, which fails for my Image model (Console log was taken by iterating over FormData, as just logging FormData doesn't show its content). Note that bar the image, none of these values are required in the model:
Find below the model, the serializer and the view from DRF, as well as my Type-Script POST method to call that API:
//Typescript ImageUploadService.ts post method
postImage(imageModel: Image, imageFile: File){
const url = `${Constants.wikiApiUrl}/image/upload/`;
const formData: FormData = new FormData();
for ( var key in imageModel ) {
if (key==="image"){
formData.append("image", imageFile, imageFile.name);
} else if (imageModel[key]){
formData.append(key, imageModel[key]);
}
}
const options = {headers: new HttpHeaders({"Content-Disposition": `attachment; filename=${imageFile.name}`})}
return this.http.post(url, formData, options);
}
#serializers.py
class ImageSerializer (serializers.ModelSerializer):
image = serializers.ImageField("image.image")
class Meta:
model = wiki_models.Image
fields = [
'pk',
'image',
'name',
'character_article',
'creature_article',
'encounter_article',
'item_article',
'location_article',
'organization_article',
]
#api_views.py
class ImageUploadView(APIView):
parser_classes = (FileUploadParser,)
def post(self, request, *args, **kwargs):
image_serializer = ImageSerializer(data=request.data)
if image_serializer.is_valid():
image_serializer.save()
return Response(image_serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(image_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# models.py
class Image(models.Model):
"""A table to hold additional images for articles"""
image = models.ImageField(upload_to='article_images',
default=settings.DEFAULT_IMAGE,
max_length=400)
name = models.CharField(max_length=400, null=True, blank=True, help_text='A name describing the image')
character_article = models.ForeignKey('Character', null=True, blank=True, related_name='character_gallery_image',
on_delete=models.CASCADE)
creature_article = models.ForeignKey('Creature', null=True, blank=True, related_name='creature_gallery_image',
on_delete=models.CASCADE)
encounter_article = models.ForeignKey('Encounter', null=True, blank=True, related_name='encounter_gallery_image',
on_delete=models.CASCADE)
item_article = models.ForeignKey('Item', null=True, blank=True, related_name='item_gallery_image',
on_delete=models.CASCADE)
location_article = models.ForeignKey('Location', null=True, blank=True, related_name='location_gallery_image',
on_delete=models.CASCADE)
organization_article = models.ForeignKey('Organization', null=True, blank=True, related_name='gallery_image',
on_delete=models.CASCADE)
class Meta:
ordering = ['creature_article', 'organization_article', 'location_article',
'encounter_article', 'item_article', 'character_article']
@property
def article(self):
fields = [field for field in self._meta.fields if 'article' in field.name and getattr(self, field.name) is not None]
if len(fields) > 1:
raise AssertionError(f'{self} is linked to more than one article!')
elif len(fields) < 1:
raise AssertionError(f'{self} is not linked to an article!')
else:
target_article_fieldname = fields[0].name
return getattr(self, target_article_fieldname)
@property
def article_fieldname(self):
article_model = type(self.article).__name__.lower()
return f'{article_model}_article'
Edit:
For completion's sake and because this information would've been needed to spot the error, here the HTML and Typescript (I am using Angular's "Formly" to generate forms) of the form I was using:
<form [formGroup]="form" (ngSubmit)="cl(constants.createSignal)">
<formly-form [form]="form" [fields]="fields" [model]="model"></formly-form>
<div class="form-group">
<input type="file" (change)="onFileSelected($event)">
</div>
<button type="submit" class="btn btn-outline-secondary">Submit</button>
</form>
Upvotes: 0
Views: 1305
Reputation: 1892
There is an answer to this question after I studied all other questions with similar problems for the third time.
Since I became distrustful of my own code, I started sending requests using the Postman Desktop Client. After applying several answers again, I finally figured out that my problem had multiple facets:
FormData
Object. So I needed to put that into my form to have it in the headers of my POST request. Thus I needed to add enctype="multipart/form-data"
to my <form>
tag.ImageUploadView
needed a parser that could deal with multipart-formdata requests, as I previously was using the FileUploadParser
. Django Rest Framework comes with one, so I changed in my ImageUploadView
this line: parser_classes = (FileUploadParser,)
to parser_classes = (MultiPartParser,)
The underlying nature of the problem is the same as in this question where one received "No File was submitted" - That is what you get when you use a FileUploadParser instead of a MultiPartParser as well as getting the encoding type wrong.
Upvotes: 1