I'm building the 'edit' page of my form. This page is supposed to show form with the data that was saved, so the form is pre populated with saved data.
It works fine for most if the fields, but I have a problem with MultipleChoiceField / CheckboxSelectMultiple values that don't get restored. So instead of having the corresponding checkboxes checked with data from the saved form, they are all unchecked. Why is that ?
forms.py
class MemberForm( forms.ModelForm ):
# ......
MODEL_CATEGORIES = (
('advisor', 'advisor'),
('member', 'member'),
('admin', 'admin'),
)
model_categories = forms.MultipleChoiceField(
widget = forms.CheckboxSelectMultiple,
choices = MODEL_CATEGORIES
)
class Meta:
model = Member
fields = [ 'model_categories' ]
model
class Member( models.Model ):
model_categories = models.CharField(
max_length = 255,
null = True,
blank = True )
Controller
def profile_edit_form( request ):
user = request.user or None
# Get user member profile instance
instance = get_object_or_404( Member, author = user )
form = MemberForm( request.POST or None, instance = instance )
context = {
"form" : form,
"instance": instance
}
if form.is_valid():
# ...
return redirect( 'profile_display' )
else:
# Initial form display, and redisplay of invalid form
return render( request, 'profile_edit_form_view.html', context )
Template
<form action="/accounts/profile-edit-form/" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit"/>
</form>
You can try using initial in the form
form = MemberForm( request.POST or None, instance = instance,
initial={'model_categories': 'advisor'})
It looks as a 'feature' of Django, and won't get fixed:
https://code.djangoproject.com/ticket/28531
Solution is simple if you are using Postgres db, in your model, use an ARRAY field instead of a simple charfield:
model_categories = ArrayField( models.CharField( max_length = 200 ), blank = True, null = True )
Multi select values are properly stored and restored from this type of model field, without the need of any data processing.
Related
Is there a way to dynamically create formsets based on an API response. This would easily be done in JS, however, the app is running Django's MVT and I would like to create the form dynamically within python. Also, the API response won't come in until after the Item model is created. This form will be the second step in a flow.
models.py
class Item(models.Model):
name = models.CharField(max_length=50)
class ItemAspect(models.Model):
name = models.CharField(max_length=50)
value = models.CharField(max_length=50)
item = models.ForeignKey(Item, on_delete=models.CASCADE)
API resposne:
{
[
{
"key": "Name",
"value_list": [
"value0",
...
]
},
...
]
}
Desired form:
<form>
...
<label for="value-0"> {{ key }}</label>
<select name="value-0" id="value-0">
<option value="{{ value_list[0] }}"> {{ value_list[0] }} </option>
...
</select>
<---! The below would be handled in the view, just adding for context -->
<input style="display: hidden;" name="name-0" value="{{ key }}">
...
</form>
My best guess is that it could be done with crispy's form_helper in a view after the API response is received.
For those that might come across this, here is my current solution (also open to improvements):
forms.py
class ItemAspectForm(forms.ModelForm):
class Meta:
model = ItemAspect
exclude = ("item",)
views.py
class ItemAspectCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
model = ItemAspectForm
form_class = ItemAspectForm
template_name = "inventory/item_aspect_form.html"
success_message = _("Item aspect successfully created")
def get_context_data(self, **kwargs):
item = Item.objects.get(id=self.kwargs["id"])
aspects = [Aspect(**aspect) for aspect in get_item_aspects_for_category(item)["aspects"]]
context = super().get_context_data()
ItemAspectFormSet = modelformset_factory(
model=ItemAspect,
form=ItemAspectForm,
can_delete=True,
extra=len(aspects)
)
if self.request.POST:
context["formset"] = ItemAspectFormSet(self.request.POST)
else:
context["formset"] = ItemAspectFormSet()
for aspect, form in zip(aspects, context["formset"]):
if aspect.has_values:
form.fields["value"] = forms.ChoiceField(
choices=((value, value) for value in aspect.values)
)
form.fields["value"].required = aspect.is_required
form.fields["value"].label = aspect.name
form.fields["name"].initial = aspect.name
form.fields["name"].widget = forms.HiddenInput()
return context
def form_valid(self, form):
assert (
self.request.user.is_authenticated
) # for mypy to know that the user is authenticated
context = self.get_context_data()
formset = context["formset"]
item = Item.objects.get(id=self.kwargs["id"])
if formset.is_valid():
aspect_forms = formset.save(commit=False)
for aspect_form in aspect_forms:
aspect_form.item = item
aspect_form.save()
return super().form_valid(form)
item_aspects_create = ItemAspectCreateView.as_view()
This is probably a simple answer, but I can't seem to find it in the docs.
How do I display the value of a choice field in template tags? Using .value did not work as I thought it may.
Right now it's just displaying the Key:
user_update
when I call this template tag on my html:
{{ ticket.current_status }}
from my forms.py:
current_status = ChoiceField(
label = "",
choices = STATUS_CHOICES,
widget = Select(attrs={
'class': 'h-10 flex flex-row justify-items-left',
})
)
and my views.py:
class StaffSupportTicketUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
...
def get_context_data(self, **kwargs):
context = super(StaffSupportTicketUpdateView, self).get_context_data(**kwargs)
context['search'] = SearchForm()
context['ticket'] = self.get_object()
return context
and my models.py:
class SupportTicketModel(Model):
...
current_status = CharField(default='new_ticket', choices=STATUS_CHOICES, max_length=35)
...
finally, my STATUS_CHOICES
STATUS_CHOICES = (
('new_ticket', 'New ticket submitted'),
('user_update', 'User updated ticket'),
('need_info', "Need more information"),
('completed', 'Completed'),
)
Use the get_FOO_display method that is dynamically created for your field because it has choices, it will return the human readable value of the field
{{ ticket.get_current_status_display }}
I have a simple Django 3.1.0 app I need to create in order to assign Tasks with Tags (or assign tags into tasks).
Model
class Task(models.Model):
user = models.CharField(max_length=33)
time = models.DateTimeField(auto_now_add=True)
task = models.CharField(max_length=500)
tags = models.CharField(max_length=100, default="None", null=True)
class Tag(models.Model):
tag = models.CharField(max_length=30, default="No Tag")
members = models.ManyToManyField('Task', related_name="tag")
class Meta:
verbose_name = "tag"
verbose_name_plural = "tags"
view
def main(request):
model = Task.objects.values().all()
tags = Tag.objects.values().all()
form = TaskForm()
con = {'context': list(model), 'form': form, 'tags': list(tags)}
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
form.save()
return redirect('/')
else:
form = TaskForm()
return render(request, "tasks.html", con)
form
class TaskForm(ModelForm):
class Meta:
model = Task
fields = ['user', 'task', 'tags']
template_name = 'tasks.html'
tags = ModelMultipleChoiceField(
queryset= Tag.objects.all(),
widget=CheckboxSelectMultiple(), required=False,
)
task_form
<form method="post" class="form">
{% csrf_token %}
{{form}}
<input type="submit" value="Save">
</form>
This returns in the tags list the items listed as:
Tag object (1)
Tag object (2)
And when it saves when i press submit, it fetches in a table (in another template), the values saved in the text of <QuerySet [<Tag: Tag object (2)>]>
That's how it stores them in the database.
I have managed to extract the values as they are ('jenkins','AKS') and send them in the template using this (bootstrapvuejs) : {% for tag in tags %}<b-form-checkbox>{{tag.tag}}</b-form-checkbox>{% endfor %}, which lists them raw values perfectly.
However, when I do that modification, the form submitted is not written to database.
What am I missing?
UPDATE!
I have partly solved it by adding this into the Tag model:
def __str__(self):
return self.tag
but when it persists it on submit, it still saves it as:
<QuerySet [<Tag: jenkins>]>
So, how and where do I strip only the specific tag values to be inserted in the database?
Many Thanks
Alright so there is a couple issues with your code, first off your main view:
Change it from this:
def main(request):
model = Task.objects.values().all() # calling values without specifying an argument makes no sense so just call it like **Task.objects.all()**
tags = Tag.objects.values().all() # same here
form = TaskForm() # don't call your form here it gets reassigned later anyways
con = {'context': list(model), 'form': form, 'tags': list(tags)} # don't define your context here since you are reasigning your form later so the form instance is always TaskForm()
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
form.save()
return redirect('/')
else:
form = TaskForm()
return render(request, "tasks.html", con)
To this:
def main(request):
model = Task.objects.all()
tags = Tag.objects.all()
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
form.save()
return redirect('/')
else:
form = TaskForm()
context = {'tasks': model,
'form': form,
'tags': tags}
return render(request, "tasks.html", con)
Then in your template pass your form with as_p method call:
{{ form.as_p }}
Hovewer the error you are getting is not because of your html or your view, it's because your tags field in your Task model is not a ManyToMany relationship to your Tag model but rather a simple CharacterField and you are trying to save objects to the CharField, so rewrite your Task model like this:
class Task(models.Model):
user = models.CharField(max_length=33)
time = models.DateTimeField(auto_now_add=True)
task = models.CharField(max_length=500)
tags = models.ManyToMany(Tags)
Then your form should save them in the tags field of your Task instance and you can view them like this:
task = Task.objects.get(pk=1)
task_tags = task.tags.all() # stores a queryset of all tags of the queried task
and in the template:
{% for tag in task.tags.all %}
...
{% endfor %}
OK , I solved the POST data that is saved in database as Queryset, by extracting in the view where save() is called, the field 'tags' likewise:
f = form.save(commit=False)
f.tags = request.POST['tags']
form.save()
The only problem now is that I have multiple checkboxes in the form but this way it extracts only one of them, whilst I would expect it to return a list like what is printed in the request.POST : <QueryDict: {'csrfmiddlewaretoken': ['XV7HgTFiWXEnrkhqT3IsqUN2JbnT7YIH5r6fKgh2ehqeLsLMpvCPdUU4N2qwWuPk'], 'user': ['afa'], 'task': ['aff'], 'tags': ['jenkins', 'AKS']}> -> from that I call 'tags' but it saves only 'jenkins' ...
UPDATE
OK, I RTFM and saw that there is a method on the QueryDict object that can be passed to request.POST.getlist('tags') , so now it returns the complete value of 'tags' key.
I made a form and there I had a multiple-choice field called artists which I got from my database and while adding a song a user can select multiple artists and save the song.
The artists are a ManyToManyField in Django models.
models.py
class Artists(models.Model):
""" Fields for storing Artists Data """
artist_name = models.CharField(max_length = 50, blank = False)
dob = models.DateField()
bio = models.TextField(max_length = 150)
def __str__(self):
return self.artist_name
class Songs(models.Model):
""" Fields for storing song data """
song_name = models.CharField(max_length = 30, blank = False)
genre = models.CharField(max_length = 30, blank = False)
artist = models.ManyToManyField(Artists)
release_date = models.DateField()
forms.py
class Song_input(forms.Form):
queryset = Artists.objects.only('artist_name')
OPTIONS = []
for i in queryset:
s = []
s = [i, i]
OPTIONS.append(s)
artist_name = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple,choices=OPTIONS)
song_name = forms.CharField()
genre = forms.CharField()
release_date = forms.DateField(widget=DateInput)
Now I want to get all the values selected from the form and save to my database. Here the artist_name may have multiple values.
I have tried using the add() and create() methods but can not figure out how to add all the data where one field (artist_name) having multiple data to my database.
I strongly advise to make use of a ModelForm [Django-doc]. Especially since you make use of ManyToManyFields, which are more cumbersome to save yourself.
# app/forms.py
from django import forms
class SongForm(forms.ModelForm):
class Meta:
model = Songs
fields = '__all__'
widgets = {
'artist': forms.CheckboxSelectMultiple,
'release_date': forms.DateInput
}
There is thus no need to specify the fields yourself, you can change the widgets by adding these to the widgets dictionary [Django-doc] of the Meta subclass.
In your view, you can then both render and sae objects with that form:
# app/views.py
from app.forms import SongForm
def add_song(request):
if request.method == 'POST':
form = SongForm(request.POST, request.FILES)
if form.is_valid():
form.save()
return redirect('name-of-some-view')
else:
form = SongForm()
return render(request, 'some-template.html', {'form': form})
The form.save() will save the object in the database.
In the template, you can then render the template:
<form method="post" action="{% url 'name-of-add_song-view' %}">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit">
</form>
I want to pre-populate a textfield on a django form when the form is loaded. Below is my code.
I want this "content" field in forms.py to be pre-populated when my form loads in the browser. This field should be un-editable. But with the code below an empty textfield gets created.
I know I can pre-populate this info by sending it in the url string just like I am sending 'id'. But I don't want to take that approach. Is there any other way to send parameters to forms?
forms.py
class ContentModelForm(ModelForm):
content = forms.CharField(max_length=256)
message = forms.CharField(max_length=256)
created_at = forms.DateTimeField('DateTime created')
class Meta:
model = Content
def __init__(self, *args, **kwargs):
super(ContentModelForm, self).__init__()
self.fields['content'].value = kwargs.get('content')
views.py
def post_form_upload(request, id):
post = get_object_or_404(models.Post, id=id)
content = post.content
if request.method == 'GET':
form = ContentModelForm(content = content)
else:
form = ContentModelForm(request.POST)
if form.is_valid():
message = form.cleaned_data['message']
created_at = form.cleaned_data['created_at']
models.Content.objects.create(post_id = id,
message = message,
created_at = created_at)
return HttpResponseRedirect(reverse('post_form_upload',
args = (post.next_id,)))
return render(request, 'survey_forms/post_form_upload.html',{
'form':form,
'id' : id,
})
survey_forms/post_form_upload.html
<form action="{% url 'post_form_upload' id=id %}" method='post'>
{% csrf_token %}
{{form.as_p}}
<input type='submit' value='Submit'/>
</form>
Thanks.
You should be using the initial keyword argument when instantiating your form. This accepts a dictionary where the keys are the field name and the values are the initial values.
form = ContentModelForm(initial={'content': content})
Moreover since you want this field to be readonly. You should change this line
content = forms.CharField(max_length=256)
to
content = forms.CharField(max_length=256, widget=forms.TextInput(attrs={'readonly':'readonly'})
if you want an input to be pre-populated then you might as well just give it a "value" attribute, for example.
input<type="text, value="content_you_want_in_there">