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>