Django RadioSelect Widget as Bootstrap Buttons?

Viewed 109

I have a Django reviewing and photo sharing application.

When a user submits a new Review instance, they can indicate the 'Method of Consumption' using Select widget, which by default will show choices as a dropdown. This is a very bland look.

I want to use a custom Widget which will render the choices as buttons--not radio buttons, but bootstrap buttons Bootstrap5 Radio Toggle Buttons. This is far more visually appealing.

This is how I want the Widget to look for this 'method 'ChoiceField

How do I render the widget such that for each choice, there is a button with value equal to the choice (Flower, Extract, Edible) as shown in the screenshot?

models.py

class Review(models.Model):
    title = models.CharField(max_length=35)
    content = models.TextField(max_length=500)
    strain = models.ForeignKey(
        Strain,
        on_delete=models.CASCADE,
        related_name="user_review"
    )
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.PROTECT,
        related_name="user_reviews",
        default=1
    )
    photo = models.ImageField(
        upload_to=upload_location,
        blank=True,
        null=True,
        height_field="height_field",
        width_field="width_field",
    )
    height_field = models.IntegerField(default=0, null=True)
    width_field = models.IntegerField(default=0, null=True)
    FLOWER = 'Flower'
    EXTRACT = 'Extract'
    EDIBLE = 'Edible'
    METHOD_CHOICES = (
        (FLOWER, 'Flower'),
        (EXTRACT, 'Extract'),
        (EDIBLE, 'Edible'),
    )
    method = models.CharField(
        max_length=20,
        choices=METHOD_CHOICES,
        default='Flower',
        blank=False
    )
    RATING_CHOICES = (
        (1, '1'),
        (2, '2'),
        (3, '3'),
        (4, '4'),
        (5, '5'),
    )
    rating = models.IntegerField(choices=RATING_CHOICES, default=5)
    timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
    updated = models.DateTimeField(auto_now=True, auto_now_add=False)
    users_like = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        related_name="reviews_liked",
        blank=True
    )

    class Meta:
        ordering = ["-timestamp", "-updated"]

    def get_absolute_url(self):
        return reverse("reviews:review_detail", kwargs={"id": self.id})

    def __str__(self):
        return self.title

forms.py

class ReviewForm(forms.ModelForm):
    photo = forms.ImageField(
        required=True,
    )
    FLOWER = 'Flower'
    EXTRACT = 'Extract'
    EDIBLE = 'Edible'
    METHOD_CHOICES = (
        (FLOWER, 'Flower'),
        (EXTRACT, 'Extract'),
        (EDIBLE, 'Edible'),
    )
    rating = forms.IntegerField(widget=Stars)
    method = forms.ChoiceField(choices=METHOD_CHOICES, widget=MethodSelectWidget)

    class Meta:
        model = Review
        fields = [
            "rating",
            "title",
            "method",
            "content",
            "photo",
        ]
        labels = {
            'title': '',
            'content': '',
            'method': '',
        }
        help_texts = {
            'method': 'Choose method of consumption'
        }
        widgets = {
            'title': forms.TextInput(attrs={'placeholder': 'Title'}),
            'content': forms.Textarea(attrs={'placeholder': 'Description'}),
        }

    def save(self):
        instance = super(ReviewForm, self).save(commit=False)
        instance.slug = slugify(instance.title)
        instance.save()

        return instance

widgets.py

class MethodSelectWidget(forms.Widget):
    template_name = 'partials/reviews/widgets/method_select_widget.html'

    def get_context(self, name, value, attrs=None):
        return {'widget': {
            'name': name,
            'value': value,
        }}

    def render(self, name, value, attrs=None, renderer=None):
        context = self.get_context(name, value, attrs)
        template = loader.get_template(self.template_name).render(context)
        return mark_safe(template)

method_select_widget.html

<h2>Form</h2>
<div class="btn-group btn-group-toggle" data-toggle="buttons">
    <label class="btn btn-outline-success active">
        <input type="radio" name="options" id="option1" autocomplete="off" checked>{{ widget.value }}
    </label>
</div>

review_form.html

{% extends "base.html" %}
{% block head_title %}{{ strain.name }} Review | {{ block.super }}{% endblock head_title %}
{% load crispy_forms_tags %}

{% block content %}

<div class="container mt-5">
  <div class="h-100 row justify-content-center align-items-center">
    <div class="col-sm-6">
      <h2 style="font-weight: 600;">Review {{ strain }}</h2>
      <hr/>
      <form method="POST" action="" enctype="multipart/form-data" class="form">
        {% csrf_token %}
        {{ form|crispy }}

        <button type=submit style="background-color:#6eb257; color: white; font-weight: 600; font-size: 1.2rem; letter-spacing: 1px;" 
        class="mx-auto mt-3 mb-3 btn btn-block shadow">
           Submit
        </button>
      </form>
    </div>      
  </div>
</div>

{% endblock %}
0 Answers
Related