0

I have a Django 3.2, python 3.6 website. I am having issues uploading multiple files to a model that also has a M2M field. I get an error in the save_related method at the indicated line:

ValueError: "<Image: title>" needs to have a value for field "image_id" before this many-to-many relationship can be used.

I have used this same method to upload multiple files to models without an M2M field, so I am not sure where I am going wrong.

models.py

class Tags(models.Model):
    tag_id = models.AutoField(primary_key=True)
    tag_name = models.CharField(max_length=255)    

class Image(models.Model):
    image_id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=255)
    description = models.TextField()
    original_image = models.ImageField('original_image', upload_to=settings.ORIGINAL_IMAGE_PATH,)
    exif_data = models.JSONField(default=dict)
    computed_sha256 = models.CharField(editable=False, max_length=64, default="foobar")
    tags = models.ManyToManyField(Tags, blank=True)

admin.py

class ImageForm(forms.ModelForm):
    original_image = forms.ImageField(widget=forms.FileInput(attrs={'multiple': True}))

class ImageAdmin(admin.ModelAdmin):
    form = ImageForm
    
    class Meta:
        model = Image
        fields = '__all__'  
    
    def save_related(self, request, form, *args, **kwargs):
        tags = form.cleaned_data.pop('tags', ())
        image = form.instance
        for tag in tags:
            image.tags.add(tag)   # error occurs here
        super(ImageAdmin, self).save_related(request, form, *args, **kwargs)    
    
    def save_model(self, request, obj, form, change):
        if form.is_valid():
            if not change:
                # Uploading one or more images))
                files = request.FILES.getlist('original_image')
                for f in files: 
                    image = Image()
                    if "Title" not in form.cleaned_data:
                        form.cleaned_data['Title'] = clean_title(f.name)
                    image.computed_sha256 = image_processing_utils.compute_sha256(f)
                    image.original_image = f
                    image.description = form.cleaned_data['description']
                    image.exif_data = image_processing_utils.read_exif_data(f)
                    image.save()
            else:
                pass
user1045680
  • 815
  • 2
  • 9
  • 19

1 Answers1

0

I could not find a way to upload multiple files to a model with a M2M field, so I punted and took the M2M field out of the model.

models.py

class Image(models.Model):
    image_id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=255, blank=True)
    description = models.TextField()
    original_image = models.FileField('original_image', upload_to=settings.ORIGINAL_IMAGE_PATH,)
    exif_data = models.JSONField(default=dict)
    computed_sha256 = models.CharField(editable=False, max_length=64, default="foobar")    
    
    def __str__(self):
        return self.title

    class Meta:
        db_table = 'Image'  

class ImageTags(models.Model):
    image_id = models.ForeignKey(Image, on_delete=models.CASCADE)
    tag_id = models.ForeignKey(Tags, on_delete=models.CASCADE)

    class Meta:
        db_table = 'ImageTags'  

admin.py

class ImageAdminForm(forms.ModelForm):
    original_image = forms.ImageField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
    
    def __init__(self, *args, **kwargs): 
        super(ImageAdminForm, self).__init__(*args, **kwargs)
        tag_choices = Tags.objects.values_list('tag_id', 'tag_name')
        self.fields['tags'] = forms.MultipleChoiceField(choices=tag_choices, widget=forms.SelectMultiple, required=False)  

class ImageAdmin(admin.ModelAdmin):
    list_display = ('image_id', 'title', 'description', 'views', 'original_image', 'get_tags', 'exif_data', 'created', 'updated')
    readonly_fields = ('thumb_image', 'album_image', 'display_image', 'exif_data', 'views', )
    form = ImageAdminForm
    
    class Meta:
        model = Image
        fields = '__all__'  
        
    fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('title', 'description', 'original_image',)
        }),
    )   
    
    def get_form(self, request, obj=None, **kwargs):
        # https://stackoverflow.com/questions/1057252/how-do-i-access-the-request-object-or-any-other-variable-in-a-forms-clean-met
        logger.debug("get_form START")
        kwargs['fields'] = flatten_fieldsets(self.fieldsets)
        form = super(ImageAdmin, self).get_form(request, obj, **kwargs)
        form.request_obj = request
        logger.debug("get_form END")
        return form    
    
    def get_fieldsets(self, request, obj=None):
        logger.debug("get_fieldsets START")
        import copy
        fieldsets = copy.deepcopy(super(ImageAdmin, self).get_fieldsets(request, obj))
        logger.debug("1 fieldsets=%s" % fieldsets)
        change_page_fieldset = list(fieldsets[0][1]['fields'])
        logger.debug("1 change_page_fieldset=%s" % change_page_fieldset)
    
        #if obj:
        if 'tags' not in change_page_fieldset:
            change_page_fieldset.append('tags')
            logger.debug('2 change_page_fieldset=%s' % change_page_fieldset) 
            fieldsets[0][1]['fields'] = tuple(change_page_fieldset)
            logger.debug('2 fieldsets=%s' % fieldsets)  
        
        return fieldsets

    def get_tags(self, obj):
        tag_ids = list(ImageTags.objects.filter(image_id=obj).values_list("tag_id", flat=True))
        tag_names = list(Tags.objects.filter(tag_id__in=tag_ids).values_list('tag_name', flat=True))
        return ", ".join([t for t in tag_names])  

    
    def save_model(self, request, obj, form, change):
        logger.debug("save_model START")
        logger.debug("obj=%s, change=%s, valid=%s" % (obj, change, form.is_valid()))
        logger.debug("changed fields=%s" % form.changed_data)
        logger.debug("obj.original_image=%s" % obj.original_image)
        if utils.is_celery_working():
            if form.is_valid():
                if not change:
                    # Uploading one or more images
                    logger.debug("\tvalid form")
                    logger.debug("form.cleaned_data=%s",form.cleaned_data)
                    logger.debug("files=%s" % request.FILES.getlist('original_image'))
                    files = request.FILES.getlist('original_image')
                    for f in files: 
                        image = Image()
                        if not form.cleaned_data['title']:
                            image.title = clean_title(f.name)   
                        else:
                            image.title = form.cleaned_data['title']
                        logger.debug("form.cleaned_data['title']=%s" % form.cleaned_data['title'])
                        logger.debug("f=%s" % f)
                        image.original_image = f
                        image.description = form.cleaned_data['description']
                        image.save()
                        # save the tags
                        tags = form.cleaned_data['tags']
                        for tag in tags:
                            ImageTags.objects.create(tag_id_id=int(tag), image_id_id=image.pk)
                            
                        #super().save_model(request, obj, form, change)
                else:
                    # processing a change form, so redo all the fields
                    pass
                #super().save_model(request, obj, form, change)
            else:
                # error - form is invalid
                pass
        else:
            # error - celery not working
            pass        
        logger.debug("save_model END")               
user1045680
  • 815
  • 2
  • 9
  • 19