ModelMultipleChoiceField' object has no attribute 'to_field_name' - django

These are my files:
models.py:
class Pierwszy(models.model):
name = models.CharField(max_length=15,blank =True, null= True)
extra = models.CharField(max_length=15,blank =True, null= True)
kids = models.ManyToManyField('Pierwszy', related_name="pierwszy_children", null=True, blank=True)
class Drugi(Pierwszy):
ext_name = models.CharField(max_length=15,blank =True, null= True)
views.py:
class DrugiForm(ModelForm):
def __init__(self, *args, **kwargs):
super(DrugiForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.pk:
if instance.name is not None:
self.fields['name'].widget.attrs['readonly'] = True
class Meta:
model = Drugi
fields = ('ext_name','name','kids','extra')
widgets = {
'kids' : forms.ModelMultipleChoiceField(queryset=None, widget=forms.CheckboxSelectMultiple()),
}
hidden = {
'extra'
}
template:
<form method="post">{% csrf_token %}
{{ form.non_field_errors }}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% for field in form.visible_fields %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }}: {{ field }}
</div>
{% endfor %}
<p><input type="submit" value="Send message" /></p>
</form>
On this stage I just want to see the current state of object. I don't want to edit fields.
I use queryset=None for kids' widget, because I don't want to show all possibilities, just show list of names (name field) connected to instance.
i'm not sure where should I add filter to queryset (in widget def or in init), but the biggest problem is that, whatever I do, I get
ModelMultipleChoiceField' object has no attribute 'to_field_name'
And I'm stacked now. On Google there's only one case, but this is about overriding the widget/Field - which is not my case.

The widgets dictionary expects the values to be widget instances such as TextArea(), TextInput(), etc.
If you want to use forms.ModelMultipleChoiceField, you could do something like this
class DrugiForm(ModelForm):
kids = forms.ModelMultipleChoiceField(queryset=Pierwszy.objects.none(), widget=forms.CheckboxSelectMultiple())
def __init__(self, *args, **kwargs):
super(DrugiForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.pk:
if instance.name is not None:
self.fields['name'].widget.attrs['readonly'] = True
class Meta:
model = Drugi
fields = ('ext_name','name','kids','extra')

Related

Customizing inlineformset choice in Django template

I've got some forms I'm trying to customize.
I render the fields manually - and it all works fine until get to a particular field (which is an InlineFormset itself). I'm trying to customize those options but can't seem to figure out how to do so.
my forms.py looks like this:
class SummativeScoreForm(forms.ModelForm):
subdomain_proficiency_level = forms.ModelChoiceField(
empty_label="Undecided",
queryset=SubdomainProficiencyLevel.objects.none(),
widget=forms.RadioSelect,
required=False,
)
def __init__(self, request, *args, **kwargs):
super(SummativeScoreForm, self).__init__(*args, **kwargs)
if self.instance:
if request.user == self.instance.summative.employee:
self.fields["subdomain_proficiency_level"].disabled = True
self.fields[
"subdomain_proficiency_level"
].queryset = SubdomainProficiencyLevel.objects.filter(
subdomain=self.instance.subdomain
)
self.fields[
"subdomain_proficiency_level"
].label = f"""
{self.instance.subdomain.character_code}:
{self.instance.subdomain.short_description}
"""
class Meta:
model = SummativeScore
fields = "__all__"
SummativeScoreInlineFormset = inlineformset_factory(
Summative,
SummativeScore,
fields=("subdomain_proficiency_level",),
can_delete=False,
extra=0,
form=SummativeScoreForm,
)
My template for summative_score_form looks like this:
<form method="post" novalidate>
{% csrf_token %}
{% include "myapp/includes/summative_score_response_formset_snippet.html" with formset=form %}
<button type="submit" class="btn btn-primary"><i class="fal fa-clipboard-check"></i> Submit Updated Scores</button>
</form>
The summative_score_response_formset_snippet looks like this:
{{ formset.management_form }}
{% for formset_form in formset.forms %}
{% if formset_form.non_field_errors %}
<ul>
{% for error in formset_form.non_field_errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{% for hidden_field in formset_form.hidden_fields %}
{% if hidden_field.errors %}
<ul>
{% for error in hidden_field.errors %}
<li>
(Hidden field {{ hidden_field.name }}) {{ error }}
</li>
{% endfor %}
</ul>
{% endif %}
{{ hidden_field }}
{% endfor %}
{% for field in formset_form.visible_fields %}
{% if field.name == 'subdomain_proficiency_level' %}
<label class="form-check-label" for="{{ field.id_for_label }}">
{{ field.label }}
</label>
<ul id="{{ field.auto_id }}" class="form-check mt-2">
{% for choice in formset_form.subdomain_proficiency_level %}
<div class="form-check">
<!--
THIS IS THE PART I WOULD LIKE TO CUSTOMIZE:
Unsatisfactory (name) Lorum Ipsum (description)
Satisfactory (name) Lorum Ipsum (description)
Excellent (name) Lorum Ipsum (description)
CURRENTLY IT ONLY SHOWS THE NAME
-->
{{ choice }}
</div>
{% endfor %}
</ul>
{% if field.help_text %}
<p class="help">{{ field.help_text|safe }}</p>
{% endif %}
{% else %}
{{ field }}
{% endif %}
{% endfor %}
{% endfor %}
My models look like this:
class SubdomainProficiencyLevel(CreateUpdateMixin):
"THIS IS THE 'UNSATISFACTORY' (name) 'LORUM IPSUM' (description)"
name = models.CharField(max_length=75)
description = models.TextField()
sequence = models.IntegerField()
class Meta:
ordering = ["sequence"]
verbose_name = "Subdomain Rank"
verbose_name_plural = "Subdomain Ranks"
def __str__(self):
"""
THIS IS WHAT IS 'CHOICE' IN THE FORM
I'm trying to edit this to add styles to the self.description on the form
"""
return f"{self.name}"
class SummativeScore(CreateUpdateMixin, CreateUpdateUserMixin):
summative = models.ForeignKey(Summative, on_delete=models.PROTECT)
subdomain = models.ForeignKey(Subdomain, on_delete=models.PROTECT)
subdomain_proficiency_level = models.ForeignKey(
SubdomainProficiencyLevel,
on_delete=models.PROTECT,
null=True,
blank=True,
)
class Meta:
ordering = ["subdomain__character_code"]
verbose_name = "SummativeScore"
verbose_name_plural = "SummativeScores"
def __str__(self):
"""Unicode representation of SummativeScore."""
return f"{self.subdomain_proficiency_level}"
The view is a Class Based FormView
class SummativeScoreFormView(
LoginRequiredMixin,
UserIsObserverOrObserveeMixin,
SingleObjectMixin,
FormView,
):
model = Summative
template_name = "myapp/summative_score_form.html"
pk_url_kwarg = "summative_id"
def get(self, request, *args, **kwargs):
summative_id = kwargs.pop("summative_id")
self.object = self.get_object(
queryset=Summative.objects.filter(id=summative_id)
)
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
summative_id = kwargs.pop("summative_id")
self.object = self.get_object(
queryset=Summative.objects.filter(id=summative_id)
)
return super().post(request, *args, **kwargs)
def get_form(self, form_class=None):
formset = SummativeScoreInlineFormset(
**self.get_form_kwargs(), instance=self.object
)
return formset
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["form_kwargs"] = {"request": self.request}
return kwargs
def form_valid(self, form):
form.save()
messages.success(self.request, "Changes were saved!")
return super().form_valid(form)
def form_invalid(self, form):
return super().form_invalid(form)
def get_success_url(self):
user_id = self.kwargs["user_id"]
summative_id = self.kwargs["summative_id"]
return reverse(
"myapp:summative_detail",
kwargs={
"user_id": user_id,
"summative_id": summative_id,
},
)
As you can see in the template - I render the SubdomainProficiencyLevel objects with the template variable {{ choice }}
I have tried doing {{ choice.description }} or {{ choice.name }} <span class="bold">{{ choice.description }}</span> but then nothing displays.
I have also tried adjusting the __str__ method on the model - which changes there work, but do not render as HTML (just as a string as expected).
What is the best way to customize that in the HTML?
I ended up creating a custom radio button class (similar to the documentation)
class CustomRadioSelect(forms.RadioSelect):
def create_option(
self, name, value, label, selected, index, subindex=None, attrs=None
):
option = super().create_option(
name, value, label, selected, index, subindex, attrs
)
if value:
option["attrs"]["description"] = value.instance.description
return option
Using that in the form:
subdomain_proficiency_level = forms.ModelChoiceField(
empty_label="Undecided",
queryset=SubdomainProficiencyLevel.objects.none(),
widget=CustomRadioSelect(),
required=False,
)
Then I could access it like this in the template:
{{ choice.data.attrs.description }}

Various dynamically django formsets throws missing_management_form error

I have a problem validating django formsets when i build several formsets dynamically
In this case the one client could be various brands and contact people.
models.py
class Client(ChangesMixin, models.Model):
name = models.CharField(verbose_name="Nombre", max_length=100, unique=True)
code = models.PositiveIntegerField(verbose_name="Código", blank=True)
class Meta:
verbose_name = "Cliente"
verbose_name_plural = "Clientes"
class Brand(ChangesMixin, models.Model):
name = models.CharField(verbose_name="Marca", max_length=100, blank=True, null=True)
client = models.ForeignKey('Client', verbose_name="Cliente", related_name='brand_client', on_delete=models.DO_NOTHING)
class Meta:
verbose_name = "Marca"
verbose_name_plural = "Marcas"
class Contact(ChangesMixin, models.Model):
name = models.CharField(verbose_name="Contacto", max_length=100, blank=True, null=True)
client = models.ForeignKey('Client', verbose_name="Cliente", related_name='contact_client', on_delete=models.DO_NOTHING)
class Meta:
verbose_name = "Contacto"
verbose_name_plural = "Contactos"
I have two method to create forms and formsets dynamically
forms.py
def get_custom_formset(entry_model=None, entry_fields=None, action=None):
formset = None
if action == 'create':
extra = 1
else:
extra = 0
formset = modelformset_factory(
model = entry_model,
extra = extra,
form = get_custom_form(entry_model, entry_fields, action)
return formset
def get_custom_form(entry_model=None, entry_fields=None, action=None):
class _CustomForm(forms.ModelForm):
class Meta:
model = entry_model
fields = [field.name for field in entry_fields]
def __init__(self, *args, **kwargs):
"""
"""
super(_CustomForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.pk:
for field in entry_fields:
if action == 'detail':
self.fields[field.name].widget.attrs['readonly'] = True
return _CustomForm
I have a creation class view with get and post methods, depends on model passed.
I get the model fields to build the form and if a field is a foreign key i build formsets with these concrete model.
views.py
class CustomCreateView(LoginRequiredMixin, View, PermissionRequiredMixin):
model = None
template = 'create.html'
def get(self, request, *args, **kwargs):
template_form = str(self.model._meta.verbose_name).lower() + "_form.html"
model_fields = self.model._meta.get_fields()
form = None
formset = None
formsets = {}
for main_field in model_fields:
main_field_name = main_field.__class__.__name__
if main_field_name == 'ManyToOneRel':
model_names = str(main_field.name).split("_")
submodel = apps.get_model('app', model_names[0])
submodel_fields = submodel._meta.get_fields()
formset = app_forms.get_custom_formset(submodel, submodel_fields, 'create')
queryset = submodel.objects.none()
UPDATED with SOLUTION
formset = formset(queryset=queryset, prefix=submodel.__name__.lower())
formsets[submodel._meta.verbose_name.lower()] = formset
elif main_field_name == 'ManyToManyField':
print("NOT PROVIDED YET")
form = app_forms.get_custom_form(self.model, model_fields, 'create')
form = form(prefix=self.model.__name__.lower())
return render(request, self.template, {
'form': form,
'formsets': formsets,
'template_form': template_form,
})
def post(self, request, *args, **kwargs):
template_form = str(self.model._meta.verbose_name).lower() + "_form.html"
model_fields = self.model._meta.get_fields()
for main_field in model_fields:
main_field_name = main_field.__class__.__name__
if main_field_name == 'ManyToOneRel':
model_names = str(main_field.name).split("_")
submodel = apps.get_model('app', model_names[0])
submodel_fields = submodel._meta.get_fields()
formset = app_forms.get_custom_formset(submodel, submodel_fields, 'create')
queryset = submodel.objects.none()
formset = formset(queryset=queryset, prefix=submodel.__name__.lower())
formsets[submodel.__name__.lower()] = formset
elif main_field_name == 'ManyToManyField':
print("NOT PROVIDED YET")
form = app_forms.get_custom_form(self.model, model_fields, 'create')
form = form(request.POST, prefix=self.model.__name__.lower())
for prefix, formset in formsets.items():
formset = formset.__class__(request_post, prefix=prefix)
if formset.is_valid() and form.is_valid(): HERE THROWS THE ERROR
For templates i have 3 levels to build forms and formsets dynamically
create.html
{% get_url urls 'create' as element_create %}
<form class="" action="{% url element_create %}" method="POST">
{% csrf_token %}
{% include template_form %}
{% if formsets|length > 0 %}
{% for subtemplateformname, formset in formsets.items %}
{% include 'formset.html' %}
{% endfor %}
{% endif %}
</form>
formset.html
{{ formset.management_form }}
{% for form in formset %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% include 'form.html' %}
{% endfor %}
form.html
{% load widget_tweaks %}
{% for field in form %}
<div class="form-group{% if field.errors %} has-error{% endif %}">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field|add_class:"form-control" }}
{% for error in field.errors %}
<p class="help-block">{{ error }}</p>
{% endfor %}
</div>
{% endfor %}
Firstly, when you get a formset for a related model you pass sub_fields but I have no idea where this is coming from?
formset = app_forms.get_custom_formset(submodel, sub_fields, 'create')
The error is with the way you are defining the formset prefix. In the GET you are passing model_names[0], which for a normal relationship will be the model name lowercase without any spaces. Lets use a model named MyModel for example
main_field.name # 'mymodel_set'
model_names = str(main_field.name).split("_") # ['mymodel', 'set']
model_names[0] # 'mymodel'
formset = formset(queryset=queryset, prefix=model_names[0])
When you assign the formset to the formsets dictionary you are using something different even though you are treating it the same
formsets[submodel._meta.verbose_name.lower()] = formset
...
for prefix, formset in formsets.items():
formset = formset.__class__(request_post, prefix=prefix)
submodel._meta.verbose_name will return the model name with spaces. So any models that have 2 "words" in it's verbose name will not set the correct prefix (e.g. MyModel._meta.verbose_name.lower() == 'my model' but model_names[0] == 'mymodel')

How to create and start a form Select field, without it being in the model?

I have reviewed many of the questions related to this topic, in this forum and in Spanish, I have followed the steps, and it does not work for me, on something that seems so simple, at first. I have Django 2.2.6 with a form that needs the selected value of a Select field, which is not in the model, and acquires a list of values (tuples of two) created from the view.
Upon entering the form, throw this error: "__init __ () received an unexpected keyword argument 'carpetas'", in the FotoArtForm class, in the super () line.
This is my code:
models.py
class FotoArt(models.Model):
nombre = models.CharField(max_length=50)
foto = models.ImageField(upload_to='fotos/articulos/', null=True, blank=True)
class Meta:
ordering = ['nombre']
verbose_name = _('Foto Artículo')
verbose_name_plural = _('Fotos Artículos')
def __str__(self):
return self.nombre
def get_absolute_url(self):
return reverse('detalle-fotoArt', args=[str(self.id)])
views.py
class FotoArtActualizar(LoginRequiredMixin, UpdateView):
model = FotoArt
form_class = FotoArtForm
Ruta = os.path.join(MEDIA_ROOT, 'fotos', 'articulos')
def get_form_kwargs(self):
kwargs = super(FotoArtActualizar, self).get_form_kwargs()
kwargs['carpetas'] = self.get_folders_list(self.Ruta)
return kwargs
def get_folders_list(self, ruta):
foldersList = []
for _, listaSubDir, _ in os.walk(ruta):
for dName in listaSubDir:
foldersList.append((dName, dName))
return foldersList
forms.py
class FotoArtForm(forms.ModelForm):
guardar_en_carpeta = forms.ChoiceField(choices=(), required=True)
def __init__(self, *args, **kwargs):
super(FotoArtForm, self).__init__(*args, **kwargs)
self.foldersList = kwargs.pop('carpetas', None)
self.fields['guardar_en_carpeta'].choices = self.foldersList
class Meta:
model = FotoArt
fields = ['nombre', 'guardar_en_carpeta', 'foto']
fotoArt_form.html
{% extends 'catalogo/base.html' %}
{% block content %}
<h2>Crear/Actualizar Fotos Artículos</h2>
<hr/>
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.non_field_errors }}
<div class="input-group mb-3">
<div class="input-group-prepend">
<label class="input-group-text font-weight-bold" for="inputCarpeta">Guardar en carpeta</label>
</div>
{{ form.guardar_en_carpeta.errors }}
{{ form.guardar_en_carpeta }}
</div>
{{ form.foto.errors }}
<strong>{{ form.foto.label_tag }}</strong>
{{ form.foto }}
<div class="form-group row my-2">
<label for="nombreFoto" class="col-sm-2 col-form-label"><strong>Nombre</strong></label>
{{ form.nombre.errors }}
<div class="col-sm-10">
{{ form.nombre }}
</div>
<input type="submit" value="Enviar" />
</div>
</form>
{% endblock %}
Any idea why I get this error?
Perhaps, you should make another approach to pass the list of folders (choices) to the field save_in_folder of the form; instead of overwriting the get_form_kwargs () method, I could overwrite the get_context_data () method and pass it a context ['folders_list'], but then I can't control the value of the save_in_folder selector to use it in another field.
You have not defined the carpetas field in the FotoArtForm.Meta.fields but you're passing the data for that via form hence the error from the FotoArtForm initializer as carpetas is not a valid named argument.
You need to reverse the statements to pop-out the capetas named argument so that the ModelForm.__init__ is called without it:
class FotoArtForm(forms.ModelForm):
...
...
def __init__(self, *args, **kwargs):
self.foldersList = kwargs.pop('carpetas', None)
super(FotoArtForm, self).__init__(*args, **kwargs)

Django - Custom ModelMultipleChoiceField can't categorize choices based on their parent model

EDITS AVAILABLE BELOW!
My goal:
Category1
----Option1
----Option2
--Option3
Category2
----Option1
----Option2
etc.
I have a parent model (Venue) and a child model (Amenity). A venue can have many amenities.
while configuring my initial data and presenting it with {{form.as_p}} everything works as expected.
But when I try to render my own custom form, so that I can apply a loop, It doesn't pre-populate them.
Here is my template:
<form method="POST" class="ui form">
{% csrf_token %}
{% for category in categories %}
<h4 class="ui horizontal divider header">
<i class="list icon"></i>
{{category.category}}
</h4>
<p class="ui center aligned text"><u>{{category.description}}</u></p>
{% for amenity in category.amenity_set.all %}
<div class="inline field">
<label for="choices_{{amenity.id}}"></label>
<div class="ui checkbox">
<input id="choices_{{amenity.id}}" type="checkbox" value="{{amenity.id}}" name="choices">
<label><span data-tooltip="{{amenity.description}}" data-position="top left">{{amenity}}</span></label>
</div>
</div>
{% endfor %}
{% endfor %}
<button type="submit" name="submit" class="ui button primary">Next</button>
</form>
my ModelForm:
class AmenitiesForm(ModelForm):
class Meta:
model = Venue
fields = ('choices',)
choices = forms.ModelMultipleChoiceField(Amenity.objects.all(), widget=forms.CheckboxSelectMultiple,)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if kwargs.get('instance'):
initial = kwargs.setdefault('initial', {})
initial['choices'] = [c.pk for c in kwargs['instance'].amenity_set.all()]
forms.ModelForm.__init__(self, *args, **kwargs)
def save(self, commit=True):
instance = forms.ModelForm.save(self)
instance.amenity_set.clear()
instance.amenity_set.add(*self.cleaned_data['choices'])
return instance
and my views.py:
class AddAmenitiesView(LoginRequiredMixin, CreateView):
"""
AddAmenitiesView is the view that prompts the user to select the amenities of their venue.
"""
model = Venue
form_class = AmenitiesForm
template_name = 'venues/add_amenities.html'
def parent_venue(self):
"""
returns the parent_venue based on the kwargs
:return:
"""
parent_venue = Venue.objects.get(id=self.kwargs["venue_id"])
return parent_venue
def get_initial(self):
initial = super().get_initial()
initial['choices'] = self.parent_venue().amenity_set.all()
return initial
def form_valid(self, form):
venue = Venue.objects.get(id=self.kwargs['venue_id'])
form.instance = venue
# form.instance.owner = self.request.user
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["parent_venue"] = self.parent_venue()
context["categories"] = AmenitiesCategory.objects.all()
return context
def get_success_url(self):
return reverse('add-amenities', kwargs={'venue_id': self.object.id,})
I suppose it has to do with my template since rendering the form normally, it does prepopulate the model.
Thank you for taking the time!
EDIT:
With Raydel Miranda's answer below I managed to edit the templates for how the form gets presented:
forms.py:
class CustomAmenitiesSelectMultiple(CheckboxSelectMultiple):
"""
CheckboxSelectMultiple Parent: https://docs.djangoproject.com/en/2.1/_modules/django/forms/widgets/#CheckboxSelectMultiple
checkbox_select.html: https://github.com/django/django/blob/master/django/forms/templates/django/forms/widgets/checkbox_select.html
multiple_input.html: https://github.com/django/django/blob/master/django/forms/templates/django/forms/widgets/multiple_input.html
checkbox_option.html: https://github.com/django/django/blob/master/django/forms/templates/django/forms/widgets/checkbox_option.html
input_option.html: https://github.com/django/django/blob/master/django/forms/templates/django/forms/widgets/input_option.html
"""
template_name = "forms/widgets/custom_checkbox_select.html"
option_template_name = 'forms/widgets/custom_checkbox_option.html'
class AmenitiesForm(ModelForm):
class Meta:
model = Venue
fields = ('choices',)
choices = forms.ModelMultipleChoiceField(Amenity.objects.all(), widget=CustomAmenitiesSelectMultiple,)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if kwargs.get('instance'):
initial = kwargs.setdefault('initial', {})
initial['choices'] = [c.pk for c in kwargs['instance'].amenity_set.all()]
forms.ModelForm.__init__(self, *args, **kwargs)
def save(self, commit=True):
instance = forms.ModelForm.save(self)
instance.amenity_set.clear()
instance.amenity_set.add(*self.cleaned_data['choices'])
return instance
custom_checkbox_select.html:
{% with id=widget.attrs.id %}
<div class="inline field">
<div {% if id %} id="{{ id }}" {% endif %}{% if widget.attrs.class %} class="{{ widget.attrs.class }}" {% endif %}>
{% for group, options, index in widget.optgroups %}{% if group %}
<div>
{{ group }}
<div>
{% if id %} id="{{ id }}_{{ index }}" {% endif %}>{% endif %}{% for option in options %}
<div class="checkbox">{% include option.template_name with widget=option %}</div>
{% endfor %}{% if group %}
</div>
</div>
{% endif %}{% endfor %}
</div>
</div>
{% endwith %}
custom_checkbox_option.html :
<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% endif %}{% include "django/forms/widgets/input.html" %}{% if widget.wrap_label %} {{ widget.label }}</label>
As requested, also my models.py:
class TimeStampedModel(models.Model):
"""
An abstract base class model that provides self-updating
"created" and "modified" fields.
"""
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class VenueType(TimeStampedModel):
type = models.CharField(max_length=250)
description = models.TextField()
def __str__(self):
return self.type
class Venue(TimeStampedModel):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=250)
type = models.ForeignKey(VenueType, on_delete=models.CASCADE)
total_capacity = models.PositiveIntegerField(default=0)
description = models.TextField(blank=False)
contact_number = PhoneNumberField(blank=True)
contact_email = models.EmailField(blank=True)
published = models.BooleanField(default=False)
def __str__(self):
return self.name
class AmenitiesCategory(TimeStampedModel):
category = models.CharField(max_length=250)
description = models.TextField()
def __str__(self):
return self.category
class Amenity(TimeStampedModel):
category = models.ForeignKey(AmenitiesCategory, on_delete=models.CASCADE)
venues = models.ManyToManyField(Venue, blank=True)
space = models.ManyToManyField(Space, blank=True)
name = models.CharField(max_length=250)
description = models.TextField()
def __str__(self):
return self.name
class Meta:
ordering = ['category']
You said while configuring my initial data and presenting it with {{form.as_p}} everything works as expected, if so, use {{ form.choices }} in order to render that field.
<form method="POST" class="ui form">
{% csrf_token %}
{{form.choices}}
<button type="submit" name="submit" class="ui button primary">Next</button>
</form>
Then, what you need is have a custom CheckboxSelectMultiple with its own template (in case you want a custom presentation to the user), and use it in your form:
Custom CheckboxSelectMultiple could be:
class MyCustomCheckboxSelectMultiple(CheckboxSelectMultiple):
template_name = "project/template/custom/my_checkbox_select_multiple.html"
And in the form:
class AmenitiesForm(ModelForm):
# ...
choices = forms.ModelMultipleChoiceField(Amenity.objects.all(), widget=forms.MyCustomCheckboxSelectMultiple)
# ...
How to implement the template my_checkbox_select_multiple.html, is up to you.
If you're using some Django prior to 1.11, visit this link to learn about a others things you've to do in order to customize a widget template.
Django widget override template
Hope this help!

Django - Display a ModelForm foreign key field

Model & Form
class Book(models.Model):
author = models.ForeignKey(User)
name = models.CharField(max_length=50)
class BookForm(forms.ModelForm):
class Meta:
model = Book
widgets = {
'author': forms.HiddenInput(),
}
This book form doesn't allow changing the author
Template
But I'd like to display his firstname
<form action="/books/edit" method="post">{% csrf_token %}
{{ form.author.label }}: {{ form.author.select_related.first_name }}
{{ form.as_p }}
</form>
Question
Of course form.author.select_related.first_name doesn't work
How can I display the firstname of the author ?
This should work:
<form action="/books/edit" method="post">{% csrf_token %}
{{ form.author.label }}: {{ form.instance.author.first_name }}
{{ form.as_p }}
</form>
But you cannot use this form for creating books, only for updating, this won't work if the author is not set on instance.
How about creating a read only field?
class BookForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(BookForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.id:
self.fields['author'].widget.attrs['readonly'] = True
def clean_author(self):
return self.instance.author
class Meta:
model = Book
widgets = {
'author': forms.TextInput(),
}
The clean_author method is there to prevent malicious requests trying to override the author.