Search code examples
djangodjango-formsdjango-admindjango-widget

Django custom widget, populate from another table (similar to inlines)


I am writing a custom widget for multiple image uploads. My models are:

models.py

class Room(models.Model):
    ....
    ....

class Picture (models.Model): 
    room = models.ForeignKey(Room)
    url=models.ImageField(upload_to='slider', height_field=None, width_field=None, max_length=100)

    def __unicode__(self):
        return str(self.url)

I want to create custom widget which allow multiple image upload to be shown on rooms form

This is what I tried so far:

forms.py

class MultyImageWidget(forms.Widget):

    ....

    def render(self, name, value, attrs=None):
        context = {
            'images': Picture.objects.filter(room = *room_id_of_currently_edited_room*)
            # OR
            # Any another way to get set of images from pictures table
    }
    return mark_safe(render_to_string(self.template_name, context))


class RoomsForm(forms.ModelForm):

    gallery = forms.ImageField(widget=MultyImageWidget, required=False)

    class Meta:
        model = Room
        fields = '__all__'

So problem is, I don't have gallery field in room model but I want to use widget to manage pictures which is stored in picture table similar to how one can manage data through inlines.

How to get id of room which is currently edited from my widget? Or is there any other way to get related pictures?

Thanks


Solution

  • I believe you are using a wrong approach to your problem. Widget should only be responsible of displaying data and should not care about where that data come from. your render method accepts a value parameter, which is used to pass data to widget, so your render should look similar to this:

    class MultyImageWidget(forms.Widget):
    
        ....
    
        def render(self, name, value, attrs=None):
            context = {
                'images': value
            }
            return mark_safe(render_to_string(self.template_name, context))
    

    Now we need to pass the needed Picture queryset. We can do this via form - and that what forms are for. You provided no context and I cannot comment yet to ask for details, but I suppose you are using a view to construct this form. If it is true, let's say we have some view, then we can do it this way:

    def gallery_view(request, *args, **kwargs):
        ....
        room = # get your room (maybe you pass ID via request or via arg or kwarg)
        pictures = Picture.objects.filter(room=room) # or you can do room.picture_set.all()
        form = RoomsForm(initial={'gallery': pictures})
        ....
    

    If you are using this form with Django admin, a form has instance attribute and you can code like this:

    class RoomsForm(forms.ModelForm):
        gallery = forms.ImageField(widget=MultyImageWidget, required=False)
    
        class Meta:
            model = Room
            fields = '__all__'
    
        def __init__(self, *args, **kwargs):
            super(RoomsForm, self).__init__(*args, **kwargs) # you may omit parameters to super in python 3
            if self.instance:
                self.fields['gallery'].initial = Picture.objects.filter(room=self.instance)
    

    I hope this solves your problem