I have a django based application where I want to create a form out of key, value pairs from a model. The `Child' model consists of the following rows of data:
(<parent 1>, 'component 1', 'dummy content 1'),
(<parent 1>, 'component 2', 'dummy content 2'),
Here is are my models:
# models.py
class Parent(models.Model):
class Meta:
verbose_name = 'Parent'
db_table = "parent"
title = models.CharField(max_length=28)
def __str__(self):
return self.title
class Child(models.Model):
class Meta:
verbose_name = 'Child'
db_table = "child"
parent = models.ForeignKey(Parent, on_delete=models.CASCADE)
key = models.CharField(max_length=20)
value = models.TextField()
def __str__(self):
return self.parent
Following is the direct model to form mapping I am currently using for my other forms to keep it straight forward and simple
# forms.py
class MyForm(ModelForm):
class Meta:
model = Child
fields = () # fields go here
Then I pass this form to my view. The view page_view takes pk of the parent, gets the parent and passes it to the form. The form is then passed on to the template parent_view.html via the view.
# views.py
#login_required
def page_view(request, parent_pk):
parent = get_object_or_404(Parent, pk=pk)
my_form = MyForm(request.POST, instance=parent)
return render(request, 'parent_view.html', {
'parent': parent,
'my_form': my_form,
})
In the template I render the form like this:
<!-- page_view.html -->
{{ my_form }}
However, I would also like to write the html for this manually to add any design changes locally. I would like the forms.py MyForm to construct a form from the model by collecting key, value pairs for the provided parent.
So it should render it like this:
<form action=''>
<label for='component_1'>component 1</label>
<textarea name='component_1' type='text'>dummy content 1</textarea>
<label for='component_2'>component 2</label>
<textarea name='component_2' type='text'>dummy content 2</textarea>
</form>
But I can't seem to get my head around how to handle that in the `MyForm'. I have looked around a couple of solutions over stackoverflow but none of them point me in the right direction for this problem. If anyone has any ideas I would highly appreciate. Thanks in advance.
If there are multiple Child instances, then a single form will not be of much use, you will have to use a formset (a model formset to be precise).
As per the docs,
A formset is a layer of abstraction to work with multiple forms on the same page
# forms.py
# You can provide a text area widget for the field that you want to be displayed as a text area
class MyForm(ModelForm):
class Meta:
model = Child
fields = () # fields go here
widgets = {
'field_name': forms.Textarea(attrs={'cols': 80, 'rows': 3}),
}
ChildFormset = forms.modelformset_factory(Child, ChildForm, exclude=[], extra=0)
Then in your views, you can pass a queryset of all the objects that you want in your form
# views.py
from .forms import ChildFormset
#login_required
def page_view(request, parent_pk):
parent = get_object_or_404(Parent, pk=pk)
child_queryset = parent.child_set.all()
if request.method == 'GET':
child_formset = ChildFormset(queryset=child_queryset)
return render(request, 'parent_view.html', {
'parent': parent,
'my_formset': child_formset,
})
else:
child_formset = ChildFormset(request.POST, queryset=child_queryset)
if child_formset.is_valid():
for form in child_formset:
form.save()
# ... Do whatever else you want to do with the data
In your templates, you will then have to traverse through all the form objects in the formset. Then you can display them in whatever way you want to.
# parent_view.html
{{ child_formset.management_form }}
{% for form in child_formset %}
<div class="hidden">{{ form.id }}</div>
{% for field in form.visible_fields %}
{{ field }}
{% endfor %}
{% endfor %}
NOTE: The Foreign Key field will be displayed as a drop down for the user to select a parent object from the list of parent objects.
Related
I'm looking for some advice where to go from here. I've been working on making a Form, which dynamically generates its fields.
The form is working and generating everything correctly. However, I am having issues with how to save the actual form data. I'm looking for each field to save as a new item in a model.
The View Class from view.py
class MaintenanceCheckListForm(LoginRequiredMixin, FormView):
login_url = '/accounts/login'
template_name = 'maintenance/checklist.html'
form_class = MaintenanceCheckListForm
success_url = reverse_lazy('m-checklist')
def form_valid(self, form):
form.cleaned_data
for key, values in form:
MaintenanceCheckList.objects.create(
item = key,
is_compliant = values
)
return super().form_valid(form)
The Form from forms.py
class MaintenanceCheckListForm(forms.Form):
def __init__(self, *args, **kwargs):
super(MaintenanceCheckListForm, self).__init__(*args, **kwargs)
items = Maintenance_Item.objects.all()
CHOICES = (
('P','Compliant'),
('F','Non-Compliant'),
)
for item in items:
self.fields[str(item.name)] = forms.ChoiceField(
label=item.name,
choices=CHOICES,
widget=forms.RadioSelect,
initial='F',
)
The Model, from models.py
class MaintenanceCheckList(CommonInfo):
CHOICES = (
('P','Compliant'),
('F','Non-Compliant'),
)
id = models.AutoField(primary_key=True)
item = models.CharField(max_length=100)
is_compliant = models.CharField(max_length=20, choices= CHOICES)
I am having trouble accessing the data from the Form when it POST's. I've done some troubleshooting where I have set the values statically in the '''form_valid''' and it appears to generate the correct amounts of entires in the model. However the trouble begins when I attempt to insert the values from the POST.
I receieve the below error, which I believe it is trying to dump all the keys and values into a single item instead of looping over each key, value and creating the item.
DataError at /maintenance/checklist
value too long for type character varying(100)
Request Method: POST
Request URL: http://t1.localhost:8000/maintenance/checklist
Django Version: 3.1.6
Exception Type: DataError
Exception Value:
value too long for type character varying(100)
I'm fairly new to the world of Django (4 weeks and counting so far, and maybe 12 weeks into python). So any assistance would be amazing!
I believe you have somewhat gone on a tangent. There's a simpler solution of using Model formsets for what you want.
First if you want a custom form make that:
from django import forms
class MaintenanceCheckListComplianceForm(forms.ModelForm):
item = forms.CharField(widget = forms.HiddenInput())
is_compliant = forms.ChoiceField(
choices=MaintenanceCheckList.CHOICES,
widget=forms.RadioSelect,
initial='F',
)
class Meta:
model = MaintenanceCheckList
fields = ('item', 'is_compliant')
Next use it along with modelformset_factory in your views:
from django.forms import modelformset_factory
class MaintenanceCheckListFormView(LoginRequiredMixin, FormView): # Changed view name was a bit misleading
login_url = '/accounts/login'
template_name = 'maintenance/checklist.html'
success_url = reverse_lazy('m-checklist')
def form_valid(self, form):
instances = form.save()
return super().form_valid(form)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['queryset'] = MaintenanceCheckList.objects.none()
kwargs['initial'] = [{'item': obj['name'], 'is_compliant': 'F'} for obj in Maintenance_Item.objects.all().values('name')]
return kwargs
def get_form(self, form_class=None):
kwargs = self.get_form_kwargs()
extra = len(kwargs['initial'])
form_class = modelformset_factory(MaintenanceCheckList, form=MaintenanceCheckListComplianceForm, extra=extra)
return form_class(**kwargs)
Now in your template:
<form method="post">
{{ form }}
</form>
Or manually render it:
<form method="post">
{{ form.management_form }}
{% for sub_form in form %}
Item: {{ sub_form.item.value }}
{{ sub_form }}
{% endfor %}
</form>
Note: The above usage is a bit weird due to the naming of the formset variable as form by the FormView you should look into improving that a bit.
Note: Looking at the implementation it feels a bit weird to do this. I would advice you to redesign your models a bit. Perhaps a foreign key between your models? It basically feels like you have duplicate data with this implementation.
I have an edit view for when a user wants to edit a Post:
def edit(request, id):
post = get_object_or_404(Post, id=id)
edit_form = PostForm(request.POST or None, instance=post)
if edit_form.is_valid():
instance = edit_form.save(commit=False)
instance.save(update_fields=['content'])
return HttpResponseRedirect('/')
else:
print(edit_form.errors)
edit_form = PostForm(instance=post)
context = {
'edit_form': edit_form,
'form_post': post
}
return render(request, 'edit.html', context)
When a user edits a Post, I only want them to be able to edit 1 field (content), so i've only rendered that form field in my template (pre-populated with the previous post.content. The other fields are just fields of the object (not a form/can't be edited).
...
<form method="post" action="" enctype="multipart/form-data">{% csrf_token %}
<h1>{{ form_post.title }}</h1>
<p>{{ edit_form.content}}</p>
<p>{{ form_post.category }}</p>
</form>
...
and here is my Post model:
class Post(models.Model):
...
title = models.TextField(max_length=76)
content = models.TextField(null=False, default='')
category = models.CharField(max_length=20, choices=CATEGORY_CHOICES, default='1')
When the edit form is submitted, form_errors returns this:
<ul class="errorlist">
<li>title<ul class="errorlist"><li>This field is required.</li></ul></li>
<li>category<ul class="errorlist"><li>This field is required.</li></ul</li>
</ul>
Why is this happening? Doesn't:
instance = edit_form.save(commit=False)
instance.save(update_fields=['content'])
keep the fields from the orignal Post and just change the content field?
If you only want some of the fields to be editable, you should set fields in your model form. If you use PostForm in another view and cannot edit fields, then create a new form.
class EditPostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['content']
You can subclass PostForm if you prefer:
class EditPostForm(PostForm):
class Meta(PostForm.Meta):
fields = ['content']
Then update your edit view to use EditPostForm instead of PostForm.
How would I write a view with a single form to add a new person, and on that same form, be able to add 0 or 1 or 2 or ... 68757857 images of that same person?
Please refer to the code below.
In models.py:
class Person(models.Model):
name = models.CharField(max_length=50)
class Image(models.Model):
person = models.ForeignKey(Person)
image = models.ImageField(upload_to='images/')
In forms.py:
class PersonForm(forms.ModelForm):
class Meta:
model = Product
fields = ['name']
class ImageForm(forms.ModelForm):
class Meta:
model = Image
fields = ['person', 'image']
From what I have seen, formsets are used. I have looked at many examples on SO, read the documentation and I am still confused.
I would appreciate it if someone can provide an example of a views.py that fulfils the specification at the beginning of this question.
You could use Inline formsets.
In view.py firt of all you have to create a new person instance and then use this instance with formset:
from django.forms import inlineformset_factory
ImageFormSet = inlineformset_factory(Person, Image, fields=('image',))
def my_view(request):
form = PersonForm(request.POST or None)
if form.is_valid():
new_person = form.save()
formset = ImageFormSet(request.POST or None, request.FILES or None, instance=new_person)
if formset.is_valid():
formset.save()
return redirect('persons:main')
else:
formset = ImageFormSet(request.POST or None, request.FILES or None)
return render(request, 'my_template.html', {'formset': formset, 'form': form})
In my_template.html:
<form action="" method="post" enctype="multipart/form-data">
{{ form.as_p }}
{{ formset.management_form }}
{% for frm in formset %}
{{ frm.as_p }}
{% endfor %}
<input type="submit" value="Create">
</form>
I have three models and they serve as 'Foreignkey' to each other.
Hotel models
class Hotel(model.Models):
user= models.ForeignKey(User)
name= models.CharField(max_length=100, verbose_name='hotel_name')
address= models.CharField(max_length=100, verbose_name='hotel_address')
#more fields
Rooms models
class HotelRooms(models.Model):
hotel= models.ForeignKey(Hotel, related_name='myhotels')
slug=models.SlugField(max_length=100, unique=True)
room_name=models.CharField(max_length=100, verbose_name='Room Name')
#other fields
HotelCalendar models
class HotelCalendar(models.Model):
user=models.ForeignKey(User)
hotel=models.ForeignKey(Hotel)
hotelrooms=models.ForeignKey(HotelRooms)
#other fields
Now, I want to display all rooms that belongs to a hotel in HotelCalender form in order for the owner to select the room he/she wants to update and save.
HotelCalendar form
class HotelCalendarForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(HotelCalendarForm, self).__init__(*args, **kwargs)
self.fields['hotelrooms'].queryset= HotelRooms.objects.filter(hotel=self.instance.hotel_id)
Template
<form id="post_form" method="post" action=""
enctype="multipart/form-data">
{% csrf_token %}
{{ HotelCalendarForm.as_p }}
<input type="submit" name="submit" value="Submit" />
</form>
Views
def hotel_calendar_view(request, hotel_id):
if request.method=="POST":
form=HotelCalendarForm(request.POST)
if form.is_valid():
data=form.cleaned_data
newbookdate=HotelCalendar(
user=request.user,
hotel=Hotel.objects.get(id=hotel_id),
hotelrooms=data['hotelrooms'],)
newbookdate.save()
return render(request, 'notice.html')
#other code here
When I load the form, it won't return any value, The modelchoicefield is just blank.
What am I missing?
It seems you are trying to populate the hotelrooms field with the filtered results by the hotel when the form is loaded on a GET request. If that's the case the field wont load any data as the instance variable will be None.
For the initial data you need to pass the hotel id to the form when it is being initialized on the GET request and in the form, load the data using the hotel id and not instance.hotel_id. For example:
views.py
#login_required
def hotel_calendar_view(request, hotel_id):
if request.method=="POST":
## code to post the form data
else:
context = {
'HotelCalendarForm’: HotelCalendarForm(hotel_id=hotel_id),
'hotel': Hotel.objects.get(id=hotel_id)
}
return render(request, 'hotels/hotel_calendar.html', context)
# return default response
and then in your forms:
forms.py
class HotelCalendarForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
hotel_id = kwargs.pop(“hotel_id”, None)
super(HotelCalendarForm, self).__init__(*args, **kwargs)
if hotel_id:
self.fields['hotelrooms'].queryset=HotelRooms.objects.filter(hotel=hotel_id)
You should modify the line
self.fields['hotelrooms'].queryset = HotelRooms.objects.filter(hotel=self.instance.hotel_id)
to
self.fields['hotelrooms'].queryset = HotelRooms.objects.filter(hotel=self.instance.hotel)
When you are filtering on foreign key it expects a model instance. If you would want to filter on foreign key you would have to do it like this:
HotelRooms.objects.filter(hotel_id=self.instance.hotel_id)
If you want to know more read https://docs.djangoproject.com/ja/1.9/topics/db/queries/#field-lookups
Consider a model:
class MyModel(models.Model):
token = models.CharField(unique=True, db_index=True, max_length...)
name = models.CharField(...)
...
(Aside: The purpose of the token is to be an alternative to displaying and using the ID in URLs; it is not the primary key.)
And its form:
class MyForm(forms.ModelForm):
...
class Meta:
model = models.MyModel
fields = '__all__' # Django 1.6
And its template:
...
<form action={% url 'create_or_edit_mymodel' %} ...>{% csrf_token %}
{{ form.token.as_hidden }}
<label for="id_name">Name:</label>
{{ form.name }}
...
And, finally, its view:
def create_or_edit_mymodel(request, token=None):
# [A] Entering via a hyperlink with the token, editing existing model
if token:
m = models.MyModel.objects.filter(token=token).first()
form = forms.MyForm(instance=m)
# [B] Postback from form
elif request.POST:
form = forms.MyForm(request.POST)
# [C] Entering via a hyperlink without the token, creating new model
else:
m = create_new_mymodel(...) # somewhere else
form = forms.MyForm(instance=m)
if request.method == 'POST' and form.is_valid():
saved = form.save()
# Determine if 'Save' or 'Save & Close' was clicked... assume 'Save'...
form = forms.MyForm(instance=saved)
return shortcuts.render(request, '...', { 'form': form }, context_instance=RequestContext(request))
This doesn't work. The problem is that the model's ID doesn't seem to be available to Django, so entering the view at [A] populates the form with everything as expected, but clicking 'Save' and entering the view at [B] attempts to save a model with no ID, and the unique constraint on the 'token' field fires.
I tried adding the id field to the form:
{{ form.id.as_hidden }} # or...
{{ form.pk.as_hidden }}
But nothing gets rendered.
That view looks pretty uncomfortable to me, so I'm hoping I'm making this harder than it needs to be.
Here you should pass both request.POST and instance to form init:
# [B] Postback from form
elif request.POST:
form = forms.MyForm(request.POST, instance=instance)