How to use image and embeded link as option in django model form

I am trying to create a form in which I want the user to give some option as an image and the user has to choose in b/w them but I have no idea how to do it I place an image in HTML t shows the user the image but I want to save that image option in my database also

here is my code

class Personal_readme(models.Model):
    system_choice = [
        ('windows', 'windows'),
        ('linux', 'linux'),
        ('macOs', 'macOs'),
        ('unix', 'unix')
    ]
    work_status_Regex = RegexValidator(regex = "((http|https)://)(www.)?[a-zA-Z0-9@:%._\\+~#?&//=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%._\\+~#?&//=]*)")
    name = models.CharField(max_length=70, blank=False)
    about_me = models.CharField(max_length=100, blank=True)
    work_status =  models.CharField(max_length=70, blank=True)
    work_status_link = models.URLField(validators = [work_status_Regex], blank=True)
    system = MultiSelectField(max_length=20, choices=system_choice,max_choices=4, blank=True )


    def __str__(self):
        return self.name

as you can see I want to give the user a system choice on which they like to work on but instead of the name I want to give image option and also if a user clicks on that image it throws him to the external link about that image or that organization official website Any idea will helpful

Creating a model for system variables is more easier and easy to manage

class SystemChoice (models.Model):
    name = models.CharField(max_length=200)
    img =  models.ImageField(default='default.png', upload_to='OS_logos',null=False, blank=False)
    link = models.URLField(max_length=400)

class Personal_readme(models.Model):
    work_status_Regex = RegexValidator(regex = "((http|https)://)(www.)?[a-zA-Z0-9@:%._\\+~#?&//=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%._\\+~#?&//=]*)")
    name = models.CharField(max_length=70, blank=False)
    about_me = models.CharField(max_length=100, blank=True)
    work_status =  models.CharField(max_length=70, blank=True)
    work_status_link = models.URLField(validators = [work_status_Regex], blank=True)
    system = models.ManyToManyField(SystemChoice, blank=True)

    def __str__(self):
        return self.name

and you can just do this in your template

{% for obj in your_context_for_Personal_readme.system.all %}
        {{ obj.name  }}
        <img src="{{ obj.img.link }}">
{% endfor %}

There are multiple ways to do that depending on what exactly OP wants to do in the models, templates, etc.

In the level of models, OP can

  1. Use choices attribute in the model field. Use this method if one knows that the images won't change. OP might want to change OP's system model field to CharField or FilePathField. Read more about choices here.

  2. Create a specific model / database table with a ForeignKey, or like enes islam mentions in the other answer. I prefer this method since it gives more flexibility in case one wants to add/edit/remove images.

Once the model is covered, there are different ways one can display it. For instance

  1. Fill the form field dynamically when the form is instantiated.

  2. Render choices manually. This answer has a relatively easy to understand overview.

(1) Irrespective of whether you are using crispy_forms or not, the simplest hack would be changing the __str__() of the model SystemChoice. Widget for field 'system' being: forms.CheckboxSelectMultiple.

models.py

# other imports
from django.utils.html import format_html

# other code statements

class SystemChoice (models.Model):
    name = models.CharField(max_length=200)
    img_link = models.URLField(blank=False)
    link = models.URLField(blank=False)

    def __str__(self):
        return format_html(f"{self.name}<br><img src='{self.img_link}' width='100px' />")

enter image description here

(2) Another way would be creating a custom crispy_form Field and rendering the form using {% crispy form %}. Rendering individual field in crispy form using {{ form.system|as_crispy_field }} will not render the field as the way {% crispy form %} renders the form fields.

Here, crispy_forms templates need to be modified. So, create new templates and point to that template by creating a new crispy_form Field. In form initialisation method call FormHelper and wrap the system field with custom Field. In this case: calling {{ form.system|as_crispy_field }} in template file will not render system field correctly. So will need to call {% crispy form %}. Which means to update form template file as well.

<form action="" method="POST" enctype="multipart/form-data">
    {% crispy form %}
    <input type="submit" value="Genrate File">
</form>

forms.py

# other imports
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Field

# other code statements

class CrispyCheckboxImageSelectMultiple(Field):
    template = "<PATH_TO_APP_TEMPLATES_DIR>/checkbox_multiple_images.html"


class Personal_Readme_form(forms.ModelForm):
    class Meta:
        # other code statements
        widgets = {
            # other field widgets
            "system": forms.CheckboxSelectMultiple(),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper(self)
        self.helper['system'].wrap(CrispyCheckboxImageSelectMultiple)

checkbox_multiple_images.html

{% load crispy_forms_field %}
{% load l10n %}

{% if field.is_hidden %}
    {{ field }}
{% else %}

    <div class="form-group{% if 'form-horizontal' in form_class %} row{% endif %}">
    {% if label_class %}
        <div class="{% for offset in bootstrap_checkbox_offsets %}{{ offset }} {% endfor %}{{ field_class }}">
    {% endif %}
        
    <{% if tag %}{{ tag }}{% else %}div{% endif %} id="div_{{ field.auto_id }}" class="{% if not field|is_checkbox %}form-group{% if 'form-horizontal' in form_class %} row{% endif %}{% else %}{%if use_custom_control%}custom-control custom-checkbox{% else %}form-check{% endif %}{% endif %}{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
        {% if field.label and not field|is_checkbox and form_show_labels %}
        {# not field|is_radioselect in row below can be removed once Django 3.2 is no longer supported #}    
        <label {% if field.id_for_label and not field|is_radioselect %}for="{{ field.id_for_label }}" {% endif %}class="{% if 'form-horizontal' in form_class %}col-form-label {% endif %}{{ label_class }}{% if field.field.required %} requiredField{% endif %}">
                {{ field.label|safe }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
            </label>
        {% endif %}

        {% include '<PATH_TO_TEMPLATES_DIRECTORY_OF_YOUR_APP>/checkbox_image_options.html' %}

    </{% if tag %}{{ tag }}{% else %}div{% endif %}>

    {% if label_class %}
        </div>
    {% endif %}
    </div>
{% endif %}

checkbox_image_options.html

{% load crispy_forms_filters %}
{% load l10n %}

<div here {% if field_class %}class="{{ field_class }}"{% endif %}{% if flat_attrs %} {{ flat_attrs|safe }}{% endif %}>

    {% for group, options, index in field|optgroups %}
        {% if group %}<strong>{{ group }}</strong>{% endif %}
        {% for option in options %}
        <div class="{%if use_custom_control%}custom-control custom-checkbox{% if inline_class %} custom-control-inline{% endif %}{% else %}form-check{% if inline_class %} form-check-inline{% endif %}{% endif %}">
            <input type="checkbox" class="{%if use_custom_control%}custom-control-input{% else %}form-check-input{% endif %}{% if field.errors %} is-invalid{% endif %}" name="{{ field.html_name }}" value="{{ option.value|unlocalize }}" {% include "bootstrap4/layout/attrs.html" with widget=option %}>
            <label tester class="{%if use_custom_control%}custom-control-label{% else %}form-check-label{% endif %}" for="{{ option.attrs.id }}">
                
                {% comment %} Image tag added here {% endcomment %}
                <img src="{{ option.label }}" width="100px" />

            </label>
            {% if field.errors and forloop.last and not inline_class and forloop.parentloop.last %}
                {% include 'bootstrap4/layout/field_errors_block.html' %}
            {% endif %}
        </div>
        {% endfor %}
    {% endfor %}

    {% if field.errors and inline_class %}
    <div class="w-100 {%if use_custom_control%}custom-control custom-checkbox{% if inline_class %} custom-control-inline{% endif %}{% else %}form-check{% if inline_class %} form-check-inline{% endif %}{% endif %}">
        {# the following input is only meant to allow boostrap to render the error message as it has to be after an invalid input. As the input has no name, no data will be sent. #}
        <input type="checkbox" class="custom-control-input {% if field.errors %}is-invalid{%endif%}">
        {% include 'bootstrap4/layout/field_errors_block.html' %}
    </div>
    {% endif %}

    {% include 'bootstrap4/layout/help_text.html' %}
</div>

enter image description here

Back to Top