Edit a Key/Value Parameters list Formset - django

I'm looking for a convenient solution to create an 'edit settings' key/values page.
Parameters model :
class Parameter(models.Model):
key = models.CharField(max_length=50)
value = models.CharField(max_length=250)
showInUI = models.SmallIntegerField()
Initial Keys/Values are already inserted in table.
I load them and send them using a model formset factory using these lines :
ParameterFormSet = modelformset_factory(Parameter, extra=0, fields=('key', 'value'))
parameterFormSet = ParameterFormSet(queryset=Parameter.objects.filter(showInUI=1))
return render_to_response('config.html', {'parameterFormSet': parameterFormSet}, context_instance=RequestContext(request))
Template side, when formset is displayed, keys and values are shown as inputs.
I'd like to find a convenient way to display form keys as readonly labels and values as inputs. And, when submited, validate them according django standards.
I've read a lot of stuff, I guess the solution may be a custom widget, but I could find a reliable solution.
Thanks for reading.
EDIT :
Working solution
views.py
def config(request):
ParameterFormSet = modelformset_factory(Parameter, extra=0, fields=('value',))
if request.method == "POST":
try:
formset = ParameterFormSet(request.POST, request.FILES)
except ValidationError:
formset = None
return HttpResponse("ko")
if formset.is_valid():
formset.save()
return HttpResponse("ok")
#ParameterFormSet = modelformset_factory(Parameter, extra=0, fields=('value',))
parameterFormSet = ParameterFormSet(queryset=Parameter.objects.filter(showInUI=1))
return render_to_response('config.html', {'parameterFormSet': parameterFormSet}, context_instance=RequestContext(request))
template
<form method="post">
{% csrf_token %}
{{ parameterFormSet.management_form }}
{% for form in parameterFormSet %}
<div>
{{ form.instance.key }}
{{ form }}
</div>
{% endfor %}
<input type="submit" />
</form>

If you do not want the value to be editable, don't include it in fields when creating the form set.
ParameterFormSet = modelformset_factory(Parameter, extra=0, fields=('value',)) # don't forget the trailing comma after 'value' otherwise it's not a tuple!
In your template, you can then loop through the forms in the form set, and display the key at the same time.
{% for form in parameter_form_set %}
{{ form.instance.key }}{# display the key related to this form #}
{{ form }}{# display the form #}
{% endfor %}

Related

Manually laying out fields from an inline formset in Django

I have created an inline formset for the profile information which is added to the user form:
UserSettingsFormSet = inlineformset_factory(
User,
Profile,
form=ProfileForm,
can_delete=False,
fields=(
"title",
...
),
)
class SettingsView(UpdateView):
model = User
template_name = "dskrpt/settings.html"
form_class = UserForm
def get_object(self):
return self.request.user
def get_context_data(self, **kwargs):
context = super(SettingsView, self).get_context_data(**kwargs)
context["formset"] = UserSettingsFormSet(
instance=self.request.user, prefix="user"
)
return context
This works and calling {formset} in the template file renders the complete form for both User and Profile.
Yet, I would like to lay out the individual inputs myself. This works for fields belonging to User:
<input
type="text"
name="{{form.last_name.html_name}}"
id="{{form.last_name.auto_id}}" value="{{form.last_name.value}}">
But doing the same for fields of the formset does not work. Here the attribute formset.form.title.value appears to be empty. All other attributes, e.g. formset.form.title.auto_id exist though.
Why does {{formset}} render completely with values but values are missing individually?
To answer your questions shortly:
The reason why profile fields are not showing is that they belong to a context data formset instead of form.
If you check the source code of UpdateView, it inherits from a ModelFormMixin, which defines methods related to forms, such as get_form_class.
Why this matter?
Because get_form_class grab the attribute form_class = UserForm or from your model attribute, and pass an context variable to your template called form. Thus in your template, the form variable only refers to UserForm, not your formset.
What is a context variable?
To make it simple that is a variable passed to your template, when your view is rendering the page, it will use those variables to fill in those {{}} slots.
I am sure you also use other generic views such as ListView, and probably you have overwritten the get_context_data method. That basically does the same thing to define what data should be passed to your template.
So how to solve?
Solution A:
You need to use formset in your template instead of form, however this would be quite complicated:
<form action="" method="post" enctype="multipart/form-data">
{{ form_set.management_form }}
{{ form_set.non_form_errors }}
{% for form in formset.forms %}
{% for field in form.visible_fields %}
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
{% endfor %}
{{ form.non_field_errors }}
{% endfor %}
{# show errors #}
{% for dict in formset.errors %}
{% for error in dict.values %}
{{ error }}
{% endfor %}
{% endfor %}
</form>
Solution B:
Pass correct variable in your view, which will be easier to use:
class SettingsView(UpdateView):
model = User
#...
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
if self.request.POST:
data["formset"] = UserSettingsFormset(self.request.POST, instance=self.object)
else:
data["formset"] = UserSettingsFormset(instance=self.object)
return data
Then in your template you can simply do:
<h1>User Profile</h1>
<form method="post">{% csrf_token %}
{{ form.as_p }}
<h2>Profile</h2>
{{ formset.as_p }}
<input type="submit" value="Save">
</form>
I think in your formest the form attribute is not necessary form=ProfileForm,, by default inlineformset_factory will look for a ModelForm as form, and in this case is your User model.
Solution C: Is that really necessary to use a formset? Formest is usually used to generate multiple forms related to an object, which means should be considered to use in a many-to-one relationship.
Usually, for the case of User and Profile, they are inOneToOne relationship, namely, each user only has one profile object. In this case, you can just pass your profile form into your template:
class SettingsView(UpdateView):
model = User
template_name = "dskrpt/settings.html"
form_class = UserForm
#...
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
profile = self.request.user.profile
# your Profile model should have an one-to-one field related with user, and related name should be 'profile'
# or profile = Profile.objects.get(user=self.request.user)
data['profile_form'] = ProfileForm(instance=profile)
return data
Then in your template, you can refer to the profile form by profile_form only.
As a conclusion, I will suggest using solution C.
More detailed example for inline formsets: check this project

How to render and bind choiceFile manually in Django

Am new to Django am trying to implement a simple select tag, the values are rendered with no problem but the value is not bound to the form during submission and I keep getting the message that the value is required
Form
class UploadFileForm(forms.Form):
job_type = forms.ChoiceField(widget=forms.Select, choices=JOB_TYPES)
HTML
<div class="form-group">
<select class="browser-default custom-select">
{% for type in form.job_type %}
{{ type }}
{% endfor %}
</select>
</div>
VIEW
def simple_upload(request):
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES)
return render(request, 'upload/upload.html', {'form': form})
else:
form = UploadFileForm()
return render(request, 'upload/upload.html', {'form': form})
I also tried to do {{ form.job_type }} and this one works fine but then I can't use the required css, But I want to freely change css and style in the HTML file without referring to the form field in forms.py.
You can iterate over the subwidgets of the Select widget, and then render the option.data.label:
{% for option in form.job_type %}
<select value="{{ option.data.value }}">{{ option.data.label }}</select>
{% endfor %}
But if you however only want to apply a certain class to the Select widget, I advice you to specify this in the attrs, like:
class UploadFileForm(forms.Form):
job_type = forms.ChoiceField(
widget=forms.Select(attrs={'class': 'browser-default custom-select'}),
choices=JOB_TYPES
)

Check the form input value to existing value in Django

In in my database there are some emails those should be checked with the form value which is entered in email field
models.py
class Friend(models.Model):
email = models.EmailField(max_length=100)
forms.py
class FriendForm(forms.ModelForm):
class Meta:
model = Friend
fields = ['email']
views.py
def check(request):
if request.method == "POST":
form = FriendForm(request.POST)
if form.is_valid():
queryset = Friend.objects.all
return render(request,"two.html",{"queryset":queryset})
else:
form = FriendForm()
return render(request, 'emaill.html', {'form': form})
emaill.html
<body>
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" name="Submit">
</form>
</body>
two.html
<body>
<h1>found</h1>
{% for obj in queryset %}
{{obj.email}} </br>
{% endfor %}
</body>
when user submited any email that should be checked with models email that means with existing one if matches in should render to two.html it should show connect
if mail does not match with mail that is in database it should show no such mail
Okay, I understand what you are trying to do. You are doing an email search.
def check(request):
if request.method == "POST":
form = FriendForm(request.POST)
if form.is_valid():
email = form.cleaned_data("email")
try:
friend = Friend.objects.get(email=email)
return render(request,"email1.html",
{"friend":friend})
except Friend.DoesNotExist:
return render(request, "email1.html", {"form":form})
If you are interested in connecting them, then you should use the get method of the ModelManager (objects). That will return a single object if it exists.
In your template. As you can see I have saved some typing on an extra template by using conditions in the template.
{% if form %}
#display your form
{% else %}
#display the friend as you want
{% endif %}
I recommend you go slow and do more reading of the documentation. I am here. Note that I changed your queryset to friend. queryset is misleading as it points that you want multiple objects. So in your template you cant iterate, instead you display friend without iterating.
{{ friend }}

Django form with multiple checkboxes

I am really new to Django! I have a page that displays items with checkboxes next to them. The number of items/checkboxes varies. When a button is pressed, I want the corresponding checked item to be modified.
So far, I have tried to wrap it all in one form:
<form method="post">
{% csrf_token %}
{% for event in items %}
{{ event.eventID }}
<input type="checkbox" value="{{ event.eventID }}" name="choices">
{% endfor %}
<button type="submit">Approve</button>
</form>
I want to collect them in a Django form field. I am trying to use ModelMultipleChoiceField:
class ApproveEventForm(forms.Form):
choices = forms.ModelMultipleChoiceField(queryset = Event.objects.all(), widget=forms.CheckboxSelectMultiple())
And in my views, I want to edit the selected items:
def approve_event(request):
if request.method == "POST":
form = ApproveEventForm(request.POST)
print(form.errors)
if form.is_valid():
for item in form.cleaned_data['choices']:
item.approved = True
item.save()
else:
form = ApproveEventForm()
unapproved = Event.objects.filter(approved=False)
return render(request, 'app/approve_event.html', {'items': unapproved, 'form': form})
My form is not valid and form.errors prints: choices "" is not a valid value for a primary key.
How can I fix this? Or is there another way to access the selected items?
Edit: passed the form to the template.
Managed to fix it using MultipleChoiceField instead of ModelMultipleChoiceField. Then populated the choices with existing event IDs and passed it to the template.
In forms:
choices = forms.MultipleChoiceField(widget = forms.CheckboxSelectMultiple())
In views:
form.fields['choices'].choices = [(x.eventID, "Event ID: " + x.eventID) for x in unapproved]
Had to change some of the logic for finding and editing Event objects too.

Modify one form instance in a Django formset?

I have a Django formset in which I render four instances of a form. Each form in the formset has two fields, but I want the last (4th) instance to only show/input one field. How can I do this, or is there a better way? I tried limiting the formset to three fields and making the fourth instance its own form, but I need to validate that field1 against the field1 fields in the formset. I couldn't see how to validate a form against a simultaneously submitted formset.
views.py:
FormSet = formset_factory(MyForm, formset=BaseMyFormSet, extra=4)
.html:
<form action="" method="post">{% csrf_token %}
{{ formset.management_form }}
{{ formset.non_form_errors }}
<div>
{% for form in formset %}
{% for field in form %}
{{ field.errors }}
{{ field.label_tag }}: {{ field }}
{% endfor %}
{% endfor %}
</div>
<p><input type="submit" value="Submit" /></p>
</form>
The formset has two fields in forms.py:
class MyForm(forms.Form):
# field1
# field2
How do I make it so the first three forms in the formset have two fields, but the last only contains field1?
Try explicitly removing the field for the last form in your views.py:
def view_func(request):
FormSet = formset_factory(MyForm, formset=BaseMyFormSet, extra=4)
if request.method == 'POST':
formset = FormSet(request.POST, request.FILES)
# assuming this is a required field for the other forms
formset.forms[-1].fields['field2'].required = False
if formset.is_valid():
...
else:
formset = FormSet()
del formset.forms[-1].fields['field2']
return render(request, 'form.html', {'formset': formset})
Any further adjustments depend on your form and validation logic. BaseMyFormSet.clean() is the appropriate place for formset-wide validation and it sounds like you already have code there to compare field1 across forms.
(From a purist sense, it might be better to have the FormSet class handle this entirely, but this is easier. FormSet code is decently complex. It'd make sense to override BaseFormSet.forms() but with that #cached_property bit, you're involved with implementation details best left to Django.)