tl;dr: How do I make a form output the ‘nice’ name of the choices in a model?
I have a Django model with choices, defined like this:
class City(models.Model):
AMSTERDAM = 'AMS'
ROTTERDAM = 'ROT'
THE_HAGUE = 'THE'
UTRECHT = 'UTR'
CITY_CHOICES = (
(AMSTERDAM, 'Amsterdam'),
(ROTTERDAM, 'Rotterdam'),
(THE_HAGUE, 'The Hague'),
(UTRECHT, 'Utrecht'),
)
name = models.CharField(
max_length=3,
choices=CITY_CHOICES,
default=AMSTERDAM,
blank=False,
)
In forms.py, the widgets are defined like this:
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ['title', 'text', 'categories', 'city']
widgets = {'title': forms.TextInput(attrs={
'placeholder': 'Enter a descriptive title'}),
'text': forms.Textarea(attrs={'placeholder': 'The article'}),
'categories': forms.CheckboxSelectMultiple(),
'city': forms.RadioSelect(),
}
The form itself is rendered manually (although the same effect happens with just {{form}}:
<form action="{% url 'article-submit' %}" method="POST">
{% csrf_token %}
{{% for field in form %}
<fieldset class="article-form__field">
{% if field.name = "categories"%}
{{ field.label_tag }}
<ul id={{ field.auto_id }}>
{% for checkbox in field %}
<li>
<label for="{{ checkbox.id_for_label }}">
{{ checkbox.choice_label }}
</label>
{{ checkbox.tag }}
</li>
{% endfor %}
</ul>
{% elif field.name = "city" %}
{{ field.label_tag }}
<ul id={{ field.auto_id }}>
{% for radio in field %}
<li>
<label for="{{ radio.id_for_label }}">
{{ radio.choice_label }}
</label>
{{ radio.tag }}
</li>
{% endfor %}
</ul>
{% else %}
{{ field.label_tag }} {{ field }}
{% endif %}
</fieldset>
{% endfor %}}
<fieldset class="article-form__field">
<input class="button" type="submit" value="submit">
</fieldset>
</form>
However the output is not Amsterdam, but AMS:
<fieldset class="article-form__field">
<label for="city_0">City:</label>
<ul id=city>
<li>
<label for="city_0">
---------
</label>
<input checked="checked" id="city_0" name="city" type="radio" value="" />
</li>
<li>
<label for="city_1">
AMS
</label>
<input id="city_1" name="city" type="radio" value="1" />
</li>
etc.
In other templates, I could do this: {{city.get_name_display}}, to get the full name. How do I do this in the form?
full code is here
I got the following answer from reddit user roambe:
Since city is a foreign key, Django will use the __str__ (or __unicode__) method of the City model for the choice label.
This should make it start behaving like you want to (substitute for unicode if needed):
def __str__(self):
return self.get_name_display()
In your line 'city': forms.RadioSelect(),, you need to define the choices for it. It should look like:
from blah.models import City
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ['title', 'text', 'categories', 'city']
widgets = {'title': forms.TextInput(attrs={
'placeholder': 'Enter a descriptive title'}),
'text': forms.Textarea(attrs={'placeholder': 'The article'}),
'categories': forms.CheckboxSelectMultiple(),
'city': forms.RadioSelect(choices=City.CITY_CHOICES),
}
The radio select widget inherits from the select widget, which has a brief mention of the choices argument in the docs here: https://docs.djangoproject.com/en/1.10/ref/forms/widgets/#django.forms.Select
I'd like to extend the argument a little, with a 'work-around' solution for an inherit problem.
In my situation, the returned value never changes from 'value' to 'human-r'. I believe it depends on my db-django config: on my db the field is a FK, but on Django is a simple CharField. In this way I can't define a str method for it without make changes to the model.
I decided, I had only 2-3 choices, to override the get_status function in order to evaluates the status and return a 'constant' output (a simply IF construct).
Related
I'm trying to prevent the deletion of certain entries in the inline model views with flask-admin:
The respective inline model code looks like this:
class ModelCategoryValueInline(InlineFormAdmin):
form_columns = ('id', 'display_name', 'name')
form_edit_columns = ('id', 'display_name')
can_delete = False
form_args= dict(
display_name=dict(label='Display Name', validators=[DataRequired()]),
name=dict(label='Technical Name', validators=[DataRequired()]),
)
def on_model_delete(self, model):
# Not called at all..
print('on_model_delete', model)
if model.builtin == True:
raise ValidationError(f'[{model}] is a build-in CategoryValue and cannot be deleted.')
def on_model_change(self, form, model, is_created):
# Is called, but has model already changed... - deleted models do not get this event
if not is_created:
if form.form.name.data != model.name:
raise ValidationError(f'You cannot change the internal name of a category value!')
def on_form_prefill(self, form, id):
# not called att all
form.name.render_kw = {'disabled': 'disabled', 'readonly': True}
I haven't figured out a way to prevent deletion of inline entries. on_model_delete() is not called when deleting the inline entries. can_delete has no effect either.
How can I disable the deletion of inline entries?
Ideally I want to be able to control deletion via the on_model_delete() method and prevent only deletion of values that match certain criteria.
Even though this is not an ideal solution I've managed to achieve the desired behaviour by editing the inline_list_base.html template:
{% macro render_inline_fields(field, template, render, check=None) %}
<div class="inline-field" id="{{ field.id }}">
{# existing inline form fields #}
<div class="inline-field-list">
{% for subfield in field %}
<div id="{{ subfield.id }}" class="inline-field well well-sm">
{%- if not check or check(subfield) %}
<legend>
<small>
{{ field.label.text }} #{{ loop.index }}
<div class="pull-right">
{% if subfield.get_pk and subfield.get_pk() %}
# Added if statement to check if field is builtin and disable checkbox
{% if subfield.object_data.builtin %}
<label for="del-{{ subfield.id }}" style="display: inline"><i title="This is a built-in value and cannot be deleted" data-toggle="tooltip">Built-in</i></label>
{% else %}
<input type="checkbox" name="del-{{ subfield.id }}" id="del-{{ subfield.id }}" />
<label for="del-{{ subfield.id }}" style="display: inline">{{ _gettext('Delete?') }}</label>
{% endif %}
{% else %}
<i class="fa fa-times glyphicon glyphicon-remove"></i>
{% endif %}
</div>
</small>
</legend>
<div class='clearfix'></div>
{%- endif -%}
{{ render(subfield) }}
</div>
{% endfor %}
</div>
{# template for new inline form fields #}
<div class="inline-field-template hide">
{% filter forceescape %}
<div class="inline-field well well-sm">
<legend>
<small>{{ _gettext('New') }} {{ field.label.text }}</small>
<div class="pull-right">
<span class="fa fa-times glyphicon glyphicon-remove"></span>
</div>
</legend>
<div class='clearfix'></div>
{{ render(template) }}
</div>
{% endfilter %}
</div>
<a id="{{ field.id }}-button" href="javascript:void(0)" class="btn btn-default" onclick="faForm.addInlineField(this, '{{ field.id }}');">{{ _gettext('Add') }} {{ field.label.text }}</a>
</div>
{% endmacro %}
When placing this file in templates/admin/model/inline_list_base.html this has the desired effect and prevents the user from deleting certain fields:
I would want to have a check also at the backend but I haven't found a solution for that yet.
My solution was to use something similar to
class NoDeleteInlineModelFormList(InlineModelFormList):
def display_row_controls(self, field):
return False
class NoDeleteInlineModelConverter(InlineModelConverter):
inline_field_list_type = NoDeleteInlineModelFormList
class ParentView(sqla.ModelView):
inline_models = [Child]
inline_model_form_converter = NoDeleteInlineModelConverter
You can find more details at my repo https://github.com/mapio/Flask-Admin-Inline-Models-And-Related-Fields
I need to render checkbox form fields in the following format in html template:
<input id="tag" type="checkbox" name="check" value="1">
<label for="tag">Tag 1</label>
Currently, in my template I tried:
{{ filter.form.tags.errors }}
{% for field in filter.form.tags %}
<input id="{{ field.id_for_label }}" type="checkbox" name="check">
<label for="{{ field.id_for_label }}">{{ field.value }}</label>
{% endfor %}
This will not render {{ field.value }} (I get empty instead of Tag 1) and also is non sticky.
I also tried:
{% for field in filter.form.tags %}
{{ field.label_tag }}
{{ field }}
{% endfor %}
Which gives me nested of <label><input></input></label>, but displays everything that I need.
Is it possible to render a form such that I get the format that I'm after? (It should also preserve checked e.g. make ticks sticky).
Edit:
class TaskFilter(django_filters.FilterSet):
"""Filter for books by author"""
tags = django_filters.ModelMultipleChoiceFilter(widget=forms.CheckboxSelectMultiple, queryset=Task.tags.most_common())
title = django_filters.CharFilter(field_name='title', lookup_expr='icontains')
class Meta:
model = Task
fields = ['tags', 'title']
views.py
def task_list_filter(request):
if request.method == 'GET':
form = AdditionalForm(request.GET)
# process the form
results = Task.objects.all().order_by('-created')
f = TaskFilter(request.GET, queryset=results)
# Output final query set
results = f.qs[0:100] # only get first 100 objects
# Create pagination
# Get the view
return render(request, 'base/task_list_filter.html', {'filter': f,
'task_list': results,
'form': form })
To make sure the checkbox is in the correct state you can use the example you yourself provided with a few changes:
{% for field in filter.form.tags %}
<input type="checkbox" id="{{ field.id_for_label }}"
name="{{ field.html_name }}"
{% if field.value %}checked{% endif %}>
<label for="{{ field.id_for_label }}">{{ field.value }}</label>
{% endfor %}
And remember that you can use the {{ field.label_tag }} to generate the entire label HTML tag.
In HTML
{% for field in filter.form.tags %}
{{ field.tag }} <!-- Generates the checkbox for the field -->
{{ field.choice_label }} <!-- Generates the named label of the field -->
{% endfor %}
I learned that from this guy who has lots of helpful information specific to using Django.
I'm using the Django-Filter library !important. For tags, I'm using Django-taggit.
I built the following filter.py:
class TaskFilter(django_filters.FilterSet):
tags = django_filters.ModelMultipleChoiceFilter(widget=forms.CheckboxSelectMultiple, queryset=Task.tags.most_common())
class Meta:
model = Task
fields = ['tags']
However, when I pass this filter to the template, it doesn't render the tags properly. In particular {{ field.value }} is empty. Let's look at the following cases:
CASE 1.
# template.html
{{ filter.form.tags.errors }}
{% for field in filter.form.tags %}
<label for="{{ field.id_for_label }}"></label>
{{ field.value }}
{% endfor %}
# out
<label for="id_tags_0"></label>
<label for="id_tags_1"></label>
<label for="id_tags_2"></label>
CASE 2.
# template.html
{{ filter.form.tags.errors }}
{% for field in filter.form.tags %}
<label for="{{ field.id_for_label }}"></label>
{{ field }}
{% endfor %}
# out
<label for="id_tags_0"></label>
<label for="id_tags_0"><input type="checkbox" name="tags" value="4" id="id_tags_0">Tag 1</label>
<label for="id_tags_1"></label>
<label for="id_tags_1"><input type="checkbox" name="tags" value="1" id="id_tags_1">Tag 2</label>
<label for="id_tags_2"></label>
<label for="id_tags_2"><input type="checkbox" name="tags" value="2" id="id_tags_2">Tag 3</label>
CASE 3.
# template.html
{{ filter.form.tags.errors }}
{% for field in filter.form.tags %}
{{ field }}
{{ field.label_tag }}
{% endfor %}
#out
<label for="id_tags_0"><input type="checkbox" name="tags" value="4" id="id_tags_0">Tag 1</label>
<label for="id_tags_1"><input type="checkbox" name="tags" value="1" id="id_tags_1">Tag 2</label>
<label for="id_tags_2"><input type="checkbox" name="tags" value="2" id="id_tags_2">Tag 3</label>
I'm trying to understand why this happens. Why can't I get the values as stated in the docs
STEPS TO REPRODUCE
pip install django-filter + add 'django_filters' to APPS
pip install django-taggit + add 'taggit' to APPS
# models.py
class Task(models.Model):
title = models.CharField(max_length=100, blank=False)
tags = TaggableManager()
# Use the API to create an object.
t = Task.objects.create(title="Title")
t.tags.add("Tag 1","Tag 2")
# views.py
def view(request):
f = TaskFilter(request.GET, queryset=Task.objects.all())
return render(request, 'template.html', {'filter': f}
When you iterate over filter.form.tags you're not iterating over a set of form fields, but instead over a set of individual choices for the tags field. This is why field.value doesn't work.
This should work instead:
{{ filter.form.tags.errors }}
{% for choice in filter.form.tags %}
<label for="{{ choice.id_for_label }}"></label>
{{ choice.tag }}
{% endfor %}
Where tag is an attribute that exists on each choice, which will render the checkbox input for that choice.
This is documented in the documentation for RadioSelect:
To get more granular, you can use each radio button’s tag, choice_label and id_for_label attributes.
Further down, the documentation for CheckBoxSelectMultiple says that the same logic applies for it too.
I've been going over the docs and some StackOverflow examples but I'm still having a little trouble. In Django I created a form, the view renders it and I have it displayed as form.as_p in the HTML template. This works perfectly, but I would like to be able to customize my HTML template instead of having it displayed as form.as_p. Any examples on how to do so?
So far I have the following.
View:
#login_required
def register(request):
info = Content.objects.get(id=request.user.id)
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES, instance=info)
if form.is_valid():
info = form.save(commit=False)
info.save()
return HttpResponseRedirect('/portal/register')
else:
form = UploadFileForm(instance=info)
return render(request, 'portal/register.html', {'form': form, 'gallery': info})
Form:
class UploadFileForm(ModelForm):
logo = forms.ImageField(required=False)
image1 = forms.ImageField(required=False)
terms = forms.CharField(required=False)
class Meta:
model = Content
fields = ['user', 'logo', 'image1', 'terms']
Model:
class Content(models.Model):
user = models.ForeignKey(User)
logo = models.ImageField(upload_to=content_file_name, null=True, blank=True)
image1 = models.ImageField(upload_to=content_file_name, null=True, blank=True)
terms = models.CharField(max_length="256", blank=True)
def __unicode__(self):
return self.title
HTML Template:
<form method="POST" action="" enctype="multipart/form-data">
{% csrf_token %}
<div class="col-lg-3 col-md-4 col-xs-6 thumb">
<a class="thumbnail" href="#">
{% if gallery.logo %}
<img class="img-responsive" id="logo" src="/media/{{ gallery.logo }}" alt="">
{% else %}
<img class="img-responsive" id="logo" src="/media/images/placeholder.png" alt="">
{% endif %}
</a>
<input type="file" name="logo" id="logo" multiple>
</div>
<div class="col-lg-3 col-md-4 col-xs-6 thumb">
<a class="thumbnail" href="#">
{% if gallery.image1 %}
<img class="img-responsive" id="image1" src="/media/{{ gallery.image1 }}" alt="">
{% else %}
<img class="img-responsive" id="image1" src="/media/images/placeholder.png" alt="">
{% endif %}
</a>
<input type="file" name="image1" id="image1" multiple>
</div>
<div class="form-group">
<input type="text" name="terms" id="terms" class="form-control input-sm" placeholder="terms" value="{{ gallery.terms }}">
</div>
<input type="submit" value="Submit" />
</form>
Yes you can loop on the form fields by doing the following
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
<label>
{{ field.label_tag }}
</label>
<div>
{{ field }}
</div>
</div>
{% endfor %}
Then you can add class to the div and label tags and style the form fields
You can also use Django Widget Tweaks to add classed to the form fields.
Click here to read in more details
By using widget tweaks you simply find the field in which you want to add the class by doing this
{% load widget_tweaks %}
{{ form.name|add_class:"inputContact volunteer" }}
You can render the fields manually:
We don’t have to let Django unpack the form’s fields; we can do it
manually if we like (allowing us to reorder the fields, for example).
Each field is available as an attribute of the form using {{
form.name_of_field }}, and in a Django template, will be rendered
appropriately.
Example from the documentation:
{{ form.non_field_errors }}
<div class="fieldWrapper">
{{ form.subject.errors }}
<label for="{{ form.subject.id_for_label }}">Email subject:</label>
{{ form.subject }}
</div>
<div class="fieldWrapper">
{{ form.message.errors }}
<label for="{{ form.message.id_for_label }}">Your message:</label>
{{ form.message }}
</div>
As I said in the comments, see also Django Crispy Forms. You can achieve the same without so much markup.
I have two models.
class ArticleCategory(models.Model):
category = models.CharField(max_length=255,blank=False)
class Article(models.Model):
title = models.CharField(max_length=255,blank=False)
body = models.TextField()
pub_date = models.DateTimeField(auto_now_add=True)
category = models.ForeignKey(ArticleCategory,default=1)
Now I have to render a template and save the form for Article model. I have a foreignKey field in my Article Model and because of that I'm not able to save my article form. I want to select a category from dropdown list and save it in my Article model.
How should I code my template for this ?
My views.py function for this is:
def create(request):
if request.POST:
form = ArticleForm(request.POST, request.FILES)
if form.is_valid():
form.save()
return HttpResponseRedirect('/articles/all/')
else:
form = ArticleForm()
args={}
args.update(csrf(request))
args['categories'] = ArticleCategory.objects.all()
args['form'] = form
return render_to_response('create_article.html', args)
My template create_article.html currently looks like this:
<form role="form" action="/articles/create/" method="POST" enctype="multipart/form-data">{% csrf_token %}
<div class="row">
<div class="form-group col-lg-3">
<label></label>
<p>{{form.title}}</p>
</div>
<div class="form-group col-lg-3">
<label>Category</label>
<p>
<select id="id_category">
{% for category in categories %}
<option value="{{ category }}">{{ category.category }}</option>
{% endfor %}
</select>
</p>
</div>
<div class="clearfix"></div>
<div class="form-group col-lg-12">
<label>Body</label>
{{form.body}}
</div>
<div class="form-group col-lg-12">
<button type="submit" class="btn btn-default">Save Article</button>
</div>
</div>
</form>
You don't need to do this manually. If your ArticleForm is ModelForm and doesn't exclude category field then you can just write {{ form.category }} and get dropdown created by django automatically. It uses ModelChoiceField underneath the hood.
replace
<select id="id_category">
{% for category in categories %}
<option value="{{ category }}">{{ category.category }}</option>
{% endfor %}
</select>
with
{{ form.category }}