In my web application, I have a template with ListView that shows list of table records, that works fine.
Now I want to allow user to update fields for each row of the table.
One "easy" solution is to create an update page using UpdateView and put a link to it in each row. User can then click update, open update page ... But I am wondering if there is a way to update fields in the same table, without opening a new page.
Hope my question is clear.
thanks for your help
UPDATE (and "my" solution)
I managed to do it using formset:
in forms.py
class IssueForm(forms.ModelForm):
class Meta:
model=Issue
fields=('__all__')
IssueFormset=inlineformset_factory(IssuesList, Issue, form=IssueForm,extra=0)
in views.py
class IssuesListUpdateView(UpdateView):
model=IssuesList
fields='__all__'
# ovewrite get_context_data to add formset as an additionnal parameter
def get_context_data(self, **kwargs):
context = super(IssuesListUpdateView, self).get_context_data(**kwargs)
if self.request.POST:
# if Save All issues button submitted
if 'save_all' in self.request.POST.keys():
formset = IssueFormset(self.request.POST, instance=self.object)
# sending formset to the template
context['issues_formset'] = formset
# save in the DB (saves only what has changed)
#https://docs.djangoproject.com/en/2.1/topics/forms/modelforms/#saving-objects-in-the-formset
# if formset.is_valid():
formset.save()
# re-update context with new formset, this is need to refresh table in case of deleting an object
# without this, issue is deleted but still visible in the table
context['issues_formset'] = IssueFormset(instance=self.object)
else:
# sending formset to the template
context['issues_formset'] = IssueFormset(instance=self.object)
return context
In template:
<form method="post">{% csrf_token %}
<!-- Below line is important to avoid django exception:
[django 'ManagementForm data is missing or has been tampered with] -->
{{ issues_formset.management_form }}
<input name="save_all" type="submit" value="Save All Issues">
<div class="">
<table id="formset" class="form" border="1">
{% for form in issues_formset.forms %}
{% if forloop.first %}
<thead><tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr></thead>
{% endif %}
<tr>
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
</div>
</form>
hope it will help someone, please let me know if you have any comments
Related
I had this view that rendered a form and a formset in the same template:
class LearnerUpdateView(LearnerProfileMixin, UpdateView):
model = User
form_class = UserForm
formset_class = LearnerFormSet
template_name = "formset_edit_learner.html"
success_url = reverse_lazy('pages:home')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
learner = User.objects.get(learner=self.request.user.learner)
formset = LearnerFormSet(instance=learner)
context["learner_formset"] = formset
return context
def get_object(self, queryset=None):
user = self.request.user
return user
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
user = User.objects.get(learner=self.get_object().learner)
formsets = LearnerFormSet(self.request.POST, request.FILES, instance=user)
if form.is_valid():
for fs in formsets:
if fs.is_valid():
# Messages test start
messages.success(request, "Profile updated successfully!")
# Messages test end
fs.save()
else:
messages.error(request, "It didn't save!")
return self.form_valid(form)
return self.form_invalid(form)
Then i wanted to make it prettier and i added the select2 multicheckbox widget and the django-bootstrap-datepicker-plus
Nothing has changed elsewhere, yet when i submit the post it only saves the data relative to User and not to Learner (which relies on the formset)
According to the messages, the formset data is not validated, I don't understand why since i didn't touch the substance at all but just the appearance.
Being a beginner im probably missing something big, I thank in advance whoever can help me find out the problem.
Here below the forms and the template:
(users.forms)
class LearnerForm(forms.ModelForm):
class Meta:
model = Learner
fields = ['locations', 'organization', 'supervisor', 'intro']
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ['username', 'first_name', 'last_name', 'birthday', 'email', 'profile_pic']
widgets = {
'birthday': DatePickerInput(format='%Y-%m-%d'), }
LearnerFormSet = inlineformset_factory(User, Learner, form=LearnerForm)
template
{% extends '_base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<form action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
{{ learner_formset.management_form}}
{% for form in learner_formset %}
{% if forloop.first %}
{% for field in form.visible_fields %}
{% if field.name != 'DELETE' %}
<label for="id_{{ field.name }}">{{ field.label|capfirst }}</label>
<div id='id_{{ field.name }}' class="form-group">
{{ field.errors.as_ul }}
{{ field }}
</div>
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
<input class="btn btn-success" type="submit" value="Update"/>
</form>
{% endblock content %}
I figured it out by combing the previous commits. The problem was in the template, the formset wasn't getting validated because i had ignored the hidden fields, which i hear are a common problem when dealing with formsets.
So the correct code is as such:
{% extends '_base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<form action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
{{ learner_formset.management_form}}
{% for form in learner_formset %}
{% if forloop.first %}
{% comment %} This makes it so that it doesnt show the annoying DELETE checkbox {% endcomment %}
{% for field in form.visible_fields %}
{% if field.name != 'DELETE' %}
<label for="{{ field.name }}">{{ field.label|capfirst }}</label>
<div id="{{ field.name }}" class="form-group">
{{ field }}
{{ field.errors.as_ul }}
</div>
{% endif %}
{% endfor %}
{% endif %}
{% for field in form.visible_fields %}
{% if field.name == 'DELETE' %}
{{ field.as_hidden }}
{% else %}
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{% endif %}
{% endfor %}
{% endfor %}
<input class="btn btn-success" type="submit" value="Update"/>
</form>
{% endblock content %}
How to avoid label tag being added when calling field|bootstrap. I have the below code
filter.py
import django_filters
from .models import Issue
class IssuesFilter(django_filters.FilterSet):
summary = django_filters.CharFilter(label="Summary", lookup_expr="icontains")
class Meta:
model = Issue
Views.py
def index(request):
issues = IssuesFilter(request.GET, queryset=Issue.objects.all())
context = {
'user': request.user,
'message': 'LogedIn',
'filter': issues
}
return render(request, 'testApp/index.html', context)
index.html
{% extends "firstPage/base.html" %}
{% load bootstrap %}
{% load render_table from django_tables2 %}
{% load crispy_forms_tags %}
{% block body %}
<form method="GET">
<table>
{% for field in filter.form %}
<tr class="table table-borderless">
<td>{{ field.label_tag }}</td>
<td>{{ field|bootstrap }}</td>
</tr>
{% endfor %}
</table>
<button type="submit" class="btn btn-primary">Search</button>
</form>
{% endblock %}
When I add field|bootstrap I could see the label tag of the field is displayed. Is it possible to remove additional label tag from being added?
I know this comes very late but if anyone is dealing with the same problem you can find the answer in the documentation
https://django-bootstrap4.readthedocs.io/en/latest/templatetags.html#bootstrap4.templatetags.bootstrap4.bootstrap_field
{% bootstrap_field field show_label=False %}
I am trying to output two same forms and save them to database with different prefix. I used this post https://collingrady.wordpress.com/2008/02/18/editing-multiple-objects-in-django-with-newforms/ as an example. However I get validation error that management form is being tampered with. Could you please kindly advise how to solve it? Thank you.
Also is it possible to filter database by the prefix in this case if i want to retrieve the data later for analysis.
VIEWS.PY
from django.shortcuts import render
from .forms import modelformset_factory, AssumptionsForm
from .models import Assumptions
model_names = ['Form1', 'Form2']
def get_assumptions(request):
AssumptionsFormset = modelformset_factory(
Assumptions, form=AssumptionsForm, extra=5)
if request.method == 'POST':
formsets = [AssumptionsFormset(request.POST, prefix=thing) for thing in model_names]
if all([formset.is_valid() for formset in formsets]):
for formset in formsets:
for form in formset:
form.save()
else:
formsets = [AssumptionsFormset(request.POST, prefix=thing) for thing in model_names]
return render(request, 'assumptions.html', {'formsets': formsets})
ASSUMPTIONS.HTML
<div class="form">
<form action="" method="post">
{% csrf_token %}
{% for formset in formsets %}
{{ formset.management_form }}
{{ formset.non_form_errors.as_ul }}
<h1>{{formset.prefix}}</h1>
<table id="formset" class="form">
{% for form in formset.forms %}
{% if forloop.first %}
<thead><tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr></thead>
{% endif %}
<tr class="{% cycle 'row1' 'row2' %}">
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input type="submit" value="Submit">
{% endfor %}
</form>
</div>
MODELS.PY
from django.db import models
from django.forms import ModelForm
class Assumptions(models.Model):
Worst = models.FloatField(null=True, blank=True, default=None)
Base = models.FloatField(null=True, blank=True, default=None)
Best = models.FloatField(null=True, blank=True, default=None)
FORMS.PY
from django import forms
from django.forms import modelformset_factory, ModelForm
from .models import Assumptions
class AssumptionsForm(ModelForm):
class Meta:
model = Assumptions
fields = '__all__'
You are trying to initialize the formsets with request.POST on GET requests, which of course can't work.
Replace the second
formsets = [AssumptionsFormset(request.POST, prefix=thing) for thing in model_names]
with
formsets = [AssumptionsFormset(prefix=thing) for thing in model_names]
I have a model called Users and I have a form for that model called UsersForm. In my views.py, I created a version of UsersForm, like so
form = UsersForm()
if reqest.method == POST:
form = UsersForm(request.POST)
if form.is_valid():
form.save()
c = {}
c.update(csrf(request))
c.update({'form':form})
return render_to_response('home_page.html', c)
Now, my home_page.html is this:
<html>
<body>
<form method="post" action="">{% csrf_token %}
{{ form }}
<input type="submit" value="Register"/>
</form>
{% if form.errors %}
{% for field in form %}
<p> {{field.errors}} </p>
{% endfor %}
{% endif %}
</body>
</html>
So, what I want is, I want to display only the first error in {{ field.errors}}.
What I was thinking was something like:
{% if form.errors %}
{% for field in form %}
<p> {{field.errors}} </p> {{ break}}
{% endfor %}
{% endif %}
but there is no break in the django template language, right? I also tried thinking about using {% for field in form|slice:":1" %} but that wouldn't work either. Anyone know how to do this?
You can index lists in a django template by using the dot notation:
{{ field.errors.0 }}
Be sure to check that there is at least 1 error before doing that though, or you will get an Index out of range error.
Take the template tag route.
Here's a sample template tag:
from django.template.defaulttags import register
#register.filter(name='show_error')
def show_error(dictionary):
try:
return dictionary.values()[0][0]
except (TypeError,IndexError,AttributeError):
return 'tip: try again'
And use it in your template like so:
{% if form.errors %}{{ form.errors|show_error }}</span>{% endif %}
I'm generating ModelForms and want some granular control over how they are output in my template. Specifically, I need to add some markup to the end of each radio button in each of my select lists.
Code:
# order-form.html
{% load catname %}
<form id = "order-form">
{% for form in forms %}
<div id="gun-{{ forloop.counter }}">
{% for field in form.fields %}
<div id="{{ field }}-item" class="item">
<h3>{{ field|catname }}</h3>
{% for choice in form.field.choices %} {# <-- Help me out here #}
{{ choice.id }}
{{ choice.title }}
{% endfor %}
</div>
{% endfor %}
{% endfor %}
<button type="submit" id="standard-gun-form-submit">Continue to next step</button>
</form>
# views.py
def get_form(request):
if request.method == 'POST':
if request.POST['gun_type'] == 'standard':
forms = [StandardGunForm(prefix=p) for p in range(0,2)]
return render_to_response('main/order-form.html', {'forms' : forms,}, RequestContext(request))
# forms.py
class StandardGunForm(ModelForm):
def __init__(self, *args, **kwargs):
super(StandardGunForm, self).__init__(*args, **kwargs)
for field in self.fields:
if isinstance(self.fields[field], ModelChoiceField):
self.fields[field].empty_label = None
class Meta:
model = BaseGun
widgets = {
'FrameTuning' : RadioSelect(),
'FrameConnection' : RadioSelect(),
}
exclude = ('price')
Endgame: markup that looks like this
<form id="foo">
<div class="category">
<div class="item">
<input type="radio" name="srsbzns" value="1">Option 1</input>
<img src="http://placekitten.com/150/150">
<p>Other foo here</p>
</div>
<div class="item">
<input type="radio" name="srsbzns" value="2">Option 2</input>
<img src="http://placekitten.com/150/150">
<p>Other foo here</p>
</div>
<div class="item">
<input type="radio" name="srsbzns" value="3">Option 3</input>
<img src="http://placekitten.com/150/150">
<p>Other foo here</p>
</div>
</div>
</form>
From the shell, this returns what I want
>>> forms = [StandardGunForm(prefix=p) for p in range(0,2)]\
>>> forms[0].fields['frame_tuning'].choices.queryset
I'm surprised this is proving so challenging!
Bonus: I have DEBUG = True and Django Debug toolbar enabled. Is it possible to dump the variables to the browser, so I can see what this stuff looks like as I drill down?
Thanks!
I had to do something similar and started down this path as well. I wanted to create table rows from a ModelChoiceField where each column had a different field of the model instance (and then I'd allow filtering the table rows via JavaScript).
I couldn't find it in the Django docs, but a quick perusal of the Django source showed the way. You can get to the queryset to access the model instances like so:
<form action="{% url 'some_view' %}" method="post">
{% csrf_token %}
{% if form.non_field_errors %}
{{ form.non_field_errors }}
{% endif %}
{% for field in form %}
{{ field.label }}
{% if field.field.choices %}
{% for model_instance in field.field.choices.queryset %}
{{ model_instance.id }}
{% endfor %}
{% else %}
{{ field }}
{% endif %}
{% if field.errors %}
{{ field.errors|striptags }}
{% endif %}
{% endfor %}
<button type="submit">Submit</button>
</form>
However, at this point we've disassembled the shiny widget (in my case a CheckboxSelectMultiple) and must re-assemble the HTML form input using template code. I found no direct way to simultaneously iterate over the ModelChoiceField to access the model instance fields and get the HTML form tags for the choices.
Maybe there's a way, but I abandoned my attempt and built my own HTML form, handling all the POST data in a view. It ended up much easier that way. ModelForms are really nice and convenient, but using them for something they weren't built for can end up being more difficult.
I figured I'd post this in case anyone is trying to do it for some other reason. Hope it helps.
Very late, but I'm reading now and this is what it worked for me
{% for field in form %}
{% for x, y in field.field.choices %}
{{x}}
{{y}}
{% endfor %}
{% endfor %}
Where "x" is the id or code, and "y" is the readable value or title.
You can access the underlying model instance for each choice:
{% for choice, label in form.field_name.field.choices %}
{{ choice.value }}
{{ choice.instance }}
{{ choice.instance.instance_attribute }}
{{ label }}
{% endfor %}
{% for choice in form.field.choices %} {# <-- Help me out here #}
{{ choice.id }}
{{ choice.title }}
{% endfor %}
Look what you're doing here, you're literally trying to access a field called "field" every time in this loop, which presumably does not exist.
You need to take the field object you're iterating through, and access the choices attribute on that.
{% for field in form.fields %}
{% for choice in field.choices %}