Search code examples
djangodjango-forms

Django: Adding a field which allows multiple images


Forms.py

class SellerForm(forms.ModelForm):
username = forms.CharField()
class Meta:
model = Seller
fields = [
'username',
'first_name',
'last_name',
'country',
'image_of_seller',
'city',
'date_of_birth',
'describe_yourself'
]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['username'].initial = self.instance.seller.username
    
    @transaction.atomic
    def save(self, commit=True):
        seller = super().save(commit=False)
        user = seller.seller
        self.instance.seller.username = self.cleaned_data['username']
    
        if commit:
            seller.save()
            user.save()
    
        return seller

Views.py


class SellerUpdateView(UpdateView):
model = Seller
form_class = SellerForm
template_name = 'core/seller_update_form.html'

    def get_object(self, queryset=None):
        return self.request.user.seller

Models.py

class Seller(models.Model):
seller = models.OneToOneField(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=50, null=False, blank=False)
last_name = models.CharField(max_length=50, default=False)
country = models.CharField(max_length=50, null=False, blank=False)
identification = models.ImageField(null=False, blank=False)
image_of_seller =  models.ImageField(null=False, blank=True) # I want this to accept multiple files, which are images.
city = models.CharField(max_length=30)
date_of_birth = models.DateField(null=False, blank=False)
describe_yourself = models.TextField(null=False, blank=False)
slug = models.SlugField(unique = True, blank = True)

    def __str__(self):
        return self.seller.username
    
    def save(self, *args, **kwargs):
        self.slug = slugify(self.seller.username)
        super().save(*args, **kwargs)

I know I can create another model, with a ForeignKey to the Seller model, then have inline while creating ModelAdmin. But is there a way to have this for the form instead? I want the user to be able to update their pictures.

Update

I am thinking of something that behaves like StackedInline, like the example below. But instead rendering it not in admin, but instead in the SellerForm.

class Seller(models.Model):
    #changed from seller to user, just for readability. 
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    first_name = models.CharField(max_length=50, null=False, blank=False)
    last_name = models.CharField(max_length=50, default=False)
    country = models.CharField(max_length=50, null=False, blank=False)
    identification = models.ImageField(null=False, blank=False)
    city = models.CharField(max_length=30)
    date_of_birth = models.DateField(null=False, blank=False)
    describe_yourself = models.TextField(null=False, blank=False)
    slug = models.SlugField(unique = True, blank = True)
    age = models.IntegerField(default=0)

    @property
    def calculate_age(self):
        today = datetime.date.today()
        year = today.strftime("%Y")
        current_year = int(year)
        user_year = int(self.date_of_birth.year)
        age = current_year - user_year
        self.age = age 
        return age
    
    def __str__(self):
        return self.seller.username
    
    def save(self, *args, **kwargs):
        self.calculate_age
        self.slug = slugify(self.seller.username)
        super().save(*args, **kwargs)


class ImageOfSeller(models.Model):
    seller = models.ForeignKey(Seller, on_delete=models.CASCADE)
    Image = models.ImageField()

# Admin.py 


class ImageSellerInline(admin.StackedInline):
    model = ImageOfSeller 
    can_delete = True 
    verbose_name_plural = 'seller_image'
    extra = 0

class SellerAdmin(admin.ModelAdmin):
    inlines = [
        ImageSellerInline,
    ]
admin.site.register(Seller, SellerAdmin)

Solution

  • I found a another solution everyone.

    First I created my own field instead, which allows me to upload multiple files. MultipleFileField.

    from django.forms import ClearableFileInput, FileField 
    
    class MultipleFileInput(ClearableFileInput):
        allow_multiple_selected = True
    
    class MultipleFileField(FileField):
        def __init__(self, *args, **kwargs):
            kwargs.setdefault("widget", MultipleFileInput())
            super().__init__(*args, **kwargs)
    
        def clean(self, data, initial=None):
            single_file_clean = super().clean
            if isinstance(data, (list, tuple)):
                result = [single_file_clean(d, initial) for d in data]
            else:
                result = single_file_clean(data, initial)
            return result
    

    Then I added validate_image_file_extension which validates if the image is an actual image. validate_image_file_extension, where I use the default_validators attribute. default_validators.

    from django.forms import ClearableFileInput, FileField 
    from django.core.validators import validate_image_file_extension
    
    class MultipleFileInput(ClearableFileInput):
        allow_multiple_selected = True
    
    class MultipleFileField(FileField):
        default_validators = [validate_image_file_extension]
        def __init__(self, *args, **kwargs):
            kwargs.setdefault("widget", MultipleFileInput())
            super().__init__(*args, **kwargs)
    
        def clean(self, data, initial=None):
            single_file_clean = super().clean
            if isinstance(data, (list, tuple)):
                result = [single_file_clean(d, initial) for d in data]
            else:
                result = single_file_clean(data, initial)
            return result
    

    Then I use my field, MultipleFileField for my form, something like this image_of_seller = MultipleFileField()

    I then use cleaned_data on MultipleFileField in my form, something like this,

    def clean(self):
        # other stuff ...
        self.image_of_seller = cleaned_data_get('image_of_seller')
    

    Then I use my another model, which has ForeignKey to my User model, and create objects based on the files.

    Example

    for file in images:
        ImageSeller.objects.create(
              seller=seller_user,
              image=file,
        )