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.
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 %}
