I have this simplified model and form:
class Books(models.Model):
name = models.CharField(max_length=500)
price = models.DecimalField(max_digits=6, decimal_places=2)
default = models.BooleanField(default=False)
def __str__(self):
return self.name
class BookForm(forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['Field'] = forms.ModelChoiceField(queryset=None, empty_label=None, widget=forms.RadioSelect)
self.fields['Field'].queryset = Books.objects.all()
self.fields['Field'].initial = Books.objects.filter(default=True).first()
This will result in a RadioSelect-Form, like this:
(x) Book1
( ) Book2
( ) Book3
My problem is, how can I add the price in the RadioSelect form, that it's just visible.
It should appear after the name of the book, ideally even in a different font, which I set over a bootstrap class (e.g "text-primary") (this is not mandatory)
(x) Book1 (10 €)
( ) Book2 (20 €)
( ) Book3 (30 €)
I know i can return the name and price in the model, like
class Books(models.Model):
name = models.CharField(max_length=500)
price = models.DecimalField(max_digits=6, decimal_places=2)
default = models.BooleanField(default=False)
def __str__(self):
return '%s (%s €)' % (self.value, str(self.price))
But because of other reasons, i can not do this. I just need to return the name. Are there other ways to do this?
I even read into django-crispy-forms, but couldnt find a solution.
You can use .label_from_instance.
From the documentation:
The __str__() method of the model will be called to generate string representations of the objects for use in the field’s choices. To provide customized representations, subclass ModelChoiceField and override label_from_instance.
You can define a function that gives you the representation that you want and then set .label_from_instance on your field.
Your BookForm then looks like this:
class BookForm(forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['Field'] = forms.ModelChoiceField(queryset=None, empty_label=None, widget=forms.RadioSelect)
self.fields['Field'].queryset = Books.objects.all()
self.fields['Field'].initial = Books.objects.filter(default=True).first()
# customize how your option is rendered in the template
self.fields["Field"].label_from_instance = lambda item: f"{item} ({item.price} €)"
To apply CSS on the label add HTML. Instead of using style='...' you can also use classes so it works with Bootstrap.
self.fields["Field"].label_from_instance = lambda item: f"{item} <span style='color:red;'>({item.price} €)</span>"
For Python versions before 3.7:
self.fields["Field"].label_from_instance = lambda item: str(item) + "<span style='color:red;'>(" + str(item.price) + " €)</span>"
Then render your form in the template like this:
<form action="{% url 'books' %}" method="post">
{% csrf_token %}
{% for hidden_field in form.hidden_fields %}
{{ hidden_field }}
{% endfor %}
<ul>
{% for choice in form.Field %}
<li>{{ choice.choice_label|safe }}</li>
{% endfor %}
</ul>
<input class="btn btn-dark" type="submit" value="Submit">
</form>
Iterate over the choices for your field and then you can get your customized labels with:
{{ choice.choice_label|safe }}
The safe filter is needed so your HTML is not escaped.
Related
I have a django filter with a dependent drop down to filter car manufactures and models. The models use a charfield and pulls the cars from a db entry.
I would like a place holder to say manufacture and model on their respected fields.
I cant find much online about doing this. The only post I can find relates to using the choice field on the model which wont work for me.
filter
class CarFilterForm(forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['model'].queryset = Post.objects.none()
if 'model_manufacture_id' in self.data:
try:
model_manufacture_id = int(self.data.get('model_manufacture_id'))
self.fields['model_id'].queryset = CarModels.objects.filter(model_manufacture_id=model_manufacture_id)
except (ValueError, TypeError):
pass
class carFilter(django_filters.FilterSet):
class Meta:
model = Post
fields = 'manufacture', 'model'
form = CarFilterForm
html
<form method="get" id="AllCarsForm" data-cars-url="{% url 'ajax-allcars' %}">
{% render_field myFilter.form.manufacture class="cars-filter-widget" %}
{% render_field myFilter.form.model class="cars-filter-widget" %}
<button class="btn btn-primary" type="submit">Search</button>
</form>
models
class Manufactures(models.Model):
manufacture_id = models.AutoField(primary_key=True)
manufacture = models.CharField(max_length=55, default="OEM")
class CarModels(models.Model):
model_id = models.AutoField(primary_key=True)
model = models.CharField(max_length=55)
model_manufacture = models.ForeignKey(Manufactures, on_delete=models.CASCADE)
Try to set the empty_label for the fields:
self.fields['your_field'].empty_label = 'My custom empty label'
The simplest method of doing this is to set the model field default to one that corresponds to your fields.
Example:
class Model(models.Model):
field = models.CharField(max_length=25, choices=CHOICES,
default=DEFAULT, blank=True)
You can also do this in forms:
self.fields['field'].choices = [('', 'Placeholder Text')] + list(
self.fields['field'].choices[1:])
Hello i created this form :
class CartForm(ModelForm):
class Meta:
model = Cart
fields =( 'products',)
from theses models :
class Product(models.Model):
title = models.CharField("Titre", max_length=120)
subtitle = models.CharField("Sous-titre", max_length=250)
description = models.TextField("Description")
picture = models.ImageField(upload_to='objects/')
enabled = models.BooleanField("Activé")
class Cart(models.Model):
products = models.ManyToManyField(Product)
and i want to display on my template an list of choice with their data
So i send form from views but i don't find any way to get the products description i only get their names !
here is my view :
def home(request):
categories = Category.objects.annotate(test=Count('product')).filter(test__gt=0)
# categories = Category.objects.order_by(
# 'id')
test = CartForm()
return render(request, 'boutique.html', {"categories": categories, "test":test})
and what i tried in my template :
{% for ee in test.products %}
{{ ee.description }}
<br />
{% endfor %}
please help me
have a nice day
Ok so theres a couple issues here:
First of all you actually need to define what is gonna happen when you submit the form, so in your view do this:
views.py
def home(request):
categories = Category.objects.annotate(test=Count('product')).filter(test__gt=0)
if request.method == 'POST':
test = CartForm(request.POST)
if form.is_valid():
cart = form.save(commit=False)
for product in request.POST.getlist('products'):
cart.add(product)
else:
pass
else:
form = CartForm()
return render(request, 'boutique.html', {"categories": categories, "test": test})
then in your template you actually have to render the form (test):
boutique.html
<form method="post">
{% csrf_token %}
{{ test.as_p }}
<button type="submit"> Add Items </button>
</form>
Now you should see the list of products in template.
edit
if you want to show a different model field in your form rewrite its __str__ method like this:
def __str__(self):
return self.description # to show description
I've got two models, Question and Project. Project has a ManyToManyField referencing Question:
## models.py
class Question(models.Model):
category = models.CharField(
max_length=4000,
blank=True,
null=True
)
question = models.CharField(
max_length=4000,
blank=True,
null=True
)
class Project(models.Model):
name = models.CharField(
max_length=100,
blank=False,
)
questions = models.ManyToManyField(
Question,
blank=True,
)
From these I have a CreateView and a custom form assigning the CheckboxSelectMultiple widget to the ManyToManyField.
## views.py
class ProjectCreate(LoginRequiredMixin, CreateView):
model = Project
form_class = ProjectForm
## forms.py
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
fields = ['name','questions',]
widgets = {'questions':forms.CheckboxSelectMultiple()}
So, what I want to do now is group the individual labels and checkboxes for the questions CheckboxSelectMultiple form field according to each question's category. Something along the lines of this (not sure exactly how it would work):
{% for field in form.questions %}
{% regroup field by <INSERT MYSTERY CODE> as question_list %}
{% for category, field in question_list %}
{{ category }}
{{ field }}
{% endfor%}
{% endfor %}
EDIT:
A more detailed example of one approach I've tried:
{% for field in form.questions %}
{% regroup field by field.category as question_list %}
{% for category, fields in question_list %}
{{ category }}
{% for f in fields%}
{{ f }}
{% endfor %}
{% endfor%}
{% endfor %}
The above results in TypeError: 'BoundWidget' object is not iterable
I have been trying to figure this out for the best part of a week. I was looking for a simple solution but no luck. The following seems to achieve what you want.
## forms.py
from itertools import groupby
from django import forms
from django.forms.models import ModelChoiceIterator, ModelMultipleChoiceField
from .models import Project, Question
class GroupedModelMultipleChoiceField(ModelMultipleChoiceField):
def __init__(self, group_by_field, group_label=None, *args, **kwargs):
"""
``group_by_field`` is the name of a field on the model
``group_label`` is a function to return a label for each choice group
"""
super(GroupedModelMultipleChoiceField, self).__init__(*args, **kwargs)
self.group_by_field = group_by_field
if group_label is None:
self.group_label = lambda group: group
else:
self.group_label = group_label
def _get_choices(self):
if hasattr(self, '_choices'):
return self._choices
return GroupedModelChoiceIterator(self)
choices = property(_get_choices, ModelMultipleChoiceField._set_choices)
class GroupedModelChoiceIterator(ModelChoiceIterator):
def __iter__(self):
"""Now yields grouped choices."""
if self.field.empty_label is not None:
yield ("", self.field.empty_label)
for group, choices in groupby(
self.queryset.all(),
lambda row: getattr(row, self.field.group_by_field)):
if group is None:
for ch in choices:
yield self.choice(ch)
else:
yield (
self.field.group_label(group),
[self.choice(ch) for ch in choices])
class GroupedCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
def optgroups(self, name, value, attrs=None):
"""
The group name is passed as an argument to the ``create_option`` method (below).
"""
groups = []
has_selected = False
for index, (option_value, option_label) in enumerate(self.choices):
if option_value is None:
option_value = ''
subgroup = []
if isinstance(option_label, (list, tuple)):
group_name = option_value
subindex = 0
choices = option_label
else:
group_name = None
subindex = None
choices = [(option_value, option_label)]
groups.append((group_name, subgroup, index))
for subvalue, sublabel in choices:
selected = (
str(subvalue) in value and
(not has_selected or self.allow_multiple_selected)
)
has_selected |= selected
subgroup.append(self.create_option(
name, subvalue, sublabel, selected, index,
subindex=subindex, attrs=attrs, group=group_name,
))
if subindex is not None:
subindex += 1
return groups
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None, group=None):
"""
Added a ``group`` argument which is included in the returned dictionary.
"""
index = str(index) if subindex is None else "%s_%s" % (index, subindex)
if attrs is None:
attrs = {}
option_attrs = self.build_attrs(self.attrs, attrs) if self.option_inherits_attrs else {}
if selected:
option_attrs.update(self.checked_attribute)
if 'id' in option_attrs:
option_attrs['id'] = self.id_for_label(option_attrs['id'], index)
return {
'name': name,
'value': value,
'label': label,
'selected': selected,
'index': index,
'attrs': option_attrs,
'type': self.input_type,
'template_name': self.option_template_name,
'wrap_label': True,
'group': group,
}
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
fields = ['name', 'questions']
def __init__(self, *args, **kwargs):
super(ProjectForm, self).__init__(*args, **kwargs)
self.fields['questions'] = GroupedModelMultipleChoiceField(
group_by_field='category',
queryset=Question.objects.all(),
widget=GroupedCheckboxSelectMultiple())
Then the following should work in your template:
{% regroup form.questions by data.group as question_list %}
{% for group in question_list %}
<h6>{{ group.grouper|default:"Other Questions" }}</h6>
<ul>
{% for choice in group.list %}
<li>{{ choice }}</li>
{% endfor %}
</ul>
</div>
{% endfor %}
Make sure you add a __str__() method to your Question class so that the question names appear correctly.
You need GroupedModelMultipleChoiceField and GroupedModelChoiceIterator to ensure your choices are properly grouped within the field instance. This means you are able to use {{ form.questions }} in the template to render a structured list.
However, if you want to use the regroup tag in a template, each choice needs to have the group name as an attribute. The GroupedCheckboxSelectMultiple widget provides this.
Credit to the following page for part of the solution:
https://mounirmesselmeni.github.io/2013/11/25/django-grouped-select-field-for-modelchoicefield-or-modelmultiplechoicefield/
I will really need your help over here. I think I have read all the relevant responses to my problem but I cannot figure out how it works.
I would like to choose from the html form in django some users that belong to a specific group.
I created my model "Task", which is below:
class Task(models.Model):
Taskdetails = models.CharField(max_length=500, null=True)
asset = models.ForeignKey('Asset', null=True)
failure = models.ForeignKey('Failure', null=True)
Created_task_date = models.DateTimeField(default=timezone.now, null=True)
employee = models.ForeignKey("auth.User", null = True)
def __str__(self):
return str(self.id)
The django form is:
class TaskForm (ModelForm):
class Meta:
model = Task
fields = ('Taskdetails', 'asset', 'failure', 'employee',)
The view is:
def task_new(request):
if request.method == "POST":
task_form = TaskForm(request.POST)
subtask_form=SubtaskForm(request.POST)
task_form.employee = User.objects.filter(groups__name='supervisor')
if task_form.is_valid() and subtask_form.is_valid():
task = task_form.save()
subtask = subtask_form.save(commit=False)
task.Created_task_date = timezone.now()
task_form.employee = User.objects.filter(groups__name='supervisor')
task.save()
subtask.task=task
subtask.Created_subtask_date = timezone.now()
subtask.save()
return redirect('great_job')
else:
task_form = TaskForm()
subtask_form = SubtaskForm()
return render(request, 'TaskTrace/task_new.html', {'task_form': task_form, 'subtask_form':subtask_form})
And the relative html is
{% block content %}
<div>
<h1>New Task</h1>
<form method="POST" class="task-form">
{% csrf_token %}
Equipment with failure: {{ task_form.asset }}<br><br>
Failure Description: {{ task_form.failure }} <br><br>
Task Details: {{ task_form.Taskdetails }} <br><br>
Employee: {{ task_form.employee }}
<button type="submit" class="save btn btn-default">Open</button>
</form>
</div>
{% endblock %}
I created in the django-admin 3 users. Two of them belongs to a the group "supervisor". I would like to be shown in the template only these two users that belong to this particular group. On the contrary, all the users are thrown in the form.
Can anyone please help me to move forward? I have stuck for 3 days in this particular point.
Thanks in advnance!
On your view when creating the form you have to do something like this:
task_form = TaskForm()
task_form.fields["employee"].queryset = User.objects.filter(group__name="supervisor")
I have an form which allows a user to edit an object description.
How can I populate an object ID in a form's hidden input value.
What I done so far is I added an field called hidden_field in forms.py but it only show the hidden_field . How can I link the hidden_field with the object ID
models.py
class School(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=55)
description = models.CharField(max_length=300,blank=True)
forms.py
class SchoolDescriptionForm(forms.ModelForm):
description = forms.CharField(widget=forms.Textarea,max_length=300)
hidden_field = forms.CharField(widget=forms.HiddenInput())
class Meta:
model = School
fields = ()
views.py
def SchoolEditor(request,school_id):
school = School.objects.get(pk=school_id,user=request.user)
form = SchoolDescriptionForm(instance=school) # I want to populate the object ID
return render(request,'schooleditor.html',{'school':school,'form':form})
template
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<input type = "submit" value= "save" />
{{ form.field.as_hidden }}
</form>
Change hidden_field to id and tell Django to include the School's id.
class SchoolDescriptionForm(forms.ModelForm):
description = forms.CharField(widget=forms.Textarea,max_length=300)
id = forms.CharField(widget=forms.HiddenInput())
class Meta:
model = School
fields = ('id', 'name', 'description')
EDIT:
If you want to conserve hidden_field as name you should then add a custom init method:
def __init__(self, *args, **kwargs):
super(SchoolDescriptionForm, self).__init__(*args, **kwargs)
if self.instance:
self.fields['hidden_field'].initial = instance.id
Just pass the object id in the form initial:
def SchoolEditor(request,school_id):
initial = {}
school = School.objects.get(pk=school_id,user=request.user)
if school:
initial.update({'hidden_field': school.id})
form = SchoolDescriptionForm(instance=school, initial=initial) # I want to populate the object ID
return render(request,'schooleditor.html',{'school':school,'form':form})