Django passing Foreign Keys with multiple forms on a page - python

I am fairly new to Django and struggling a bit on how to get the primary keys from form input on a multiform view. I cannot get the keys into the database.
I have 3 models: Human, Human_notes, and Location. My form is made of these separate models as forms on one view. I thought I should collect the primary key from each form once saved and apply that to the next form data as a foreign key...
<form action="human-add" method="POST">
{% csrf_token %}
{{ human_form.as_p }}
{{ location_form.as_p }}
{{ human_notes_form.as_p }}
<button type="submit" class="save btn btn-success">Save</button>
</form>
Human has FKs to Location...:
class Human(models.Model):
intaker = models.ForeignKey(User, default=None, on_delete=models.SET_NULL, null=True)
location = models.OneToOneField('Location', on_delete=models.SET_NULL, null=True, related_name='humans')
Human_notes has FK to Human...(maybe this will become an FK in Human but originally thought many notes for one human) :
class HumanNotes(models.Model):
human = models.ForeignKey(Human, on_delete=models.SET_NULL, null=True, related_name='humans_notes')
My view is:
def human_add(request):
if request.method == 'POST':
human_form = HumanForm(request.POST)
location_form = LocationForm(request.POST)
human_notes_form = HumanNotesForm(request.POST)
if human_form.is_valid() and location_form.is_valid() and human_notes_form.is_valid():
human_form.save(commit=False)
location_form.save()
locationKey = location_form.id
human_notes_form.save(commit=False)
human_notes_form.intaker = request.user
human_notes_form.save()
noteKey = human_notes_form.id
human_form.location = locationKey
human_form.note = noteKey
human_form.intaker = request.user
human_form.save()
return redirect('/intake/success-form')
else:
context = {
'human_form': human_form,
'location_form': location_form,
'human_notes_form': human_notes_form,
}
else:
context = {
'human_form': HumanForm(),
'location_form': LocationForm(),
'human_notes_form': HumanNotesForm(),
}
return render(request, 'intake/human-add.html', context)
The only error I am getting is that 'LocationForm' object has no attribute 'id' - but I even added it explicitly (thought I should not have to and don't want it visible):
class HumanNotesForm(forms.ModelForm):
class Meta:
model = HumanNotes
fields = ['id','intaker','note']
class LocationForm(forms.ModelForm):
class Meta:
model = Location
fields = ['id','st1','st2','cty','county','state','zip','lat','long','img','img_nm','img_id']
Any guidance appreciated.

This did the trick....as did getting excellent guidance from forum.djangoproject.com. Needed to understand the diff between forms and models.
def human_add(request):
if request.method == 'POST':
human_form = HumanForm(request.POST)
location_form = LocationForm(request.POST)
human_notes_form = HumanNotesForm(request.POST)
if human_form.is_valid() and location_form.is_valid() and human_notes_form.is_valid():
loc = location_form.save()
hum = human_form.save(commit=False)
humnote = human_notes_form.save(commit=False)
hum.intaker = request.user
hum.location = loc #.id NOTE YOU NEED THE ENTIRE INSTANCE
human_form.save()
humnote.intaker = request.user
humnote.human = hum #.id NOTE YOU NEED THE ENTIRE INSTANCE
human_notes_form.save()
return redirect('/intake/success-form')
else:
context = {
'human_form': human_form,
'location_form': location_form,
'human_notes_form': human_notes_form,
}
else:
context = {
'human_form': HumanForm(),
'location_form': LocationForm(),
'human_notes_form': HumanNotesForm(),
}
return render(request, 'intake/human-add.html', context)

Related

Django Formset to respond to survey/questionnaire

In short: I have an app where a user inputs workouts. Before they add exercises to their workout, they must submit a 'readiness' questionnaire with 5-10 questions, rating each low-high. I can't work out how to list ALL ReadinessQuestions in a view, and have the user submit an answer for each of them in a formset.
Models: Model with the question set (ReadinessQuestion), and model that stores readiness_question, rating and the Workout it belongs to (WorkoutReadiness).
Form: I've made a ModelForm into a formset_factory. When I set the 'readiness_question' form field widget to be 'readonly', this makes my form invalid and ignores my 'initial' data
View: I want to create a view where the 5-10 Questions from ReadinessQuestion are in a list, with dropdowns for the Answers.
I have set initial data for my form (each different question)
the user selects the 'rating' they want for each readiness_question
a new Workout is instantiated
the WorkoutReadiness is saved, with ForeignKey to the new Workout
How my page it looks now (nearly how I want it to look): https://imgur.com/a/HD1l3oe
The error on submission of form:
Cannot assign "'Sleep'": "WorkoutReadiness.readiness_question" must be a "ReadinessQuestion" instance.
The Django documentation is really poor here, widget=forms.TextInput(attrs={'readonly':'readonly'}) isn't even on the official Django site. If I don't make the readiness_question 'readonly', the user has dropdowns which they can change (this is undesirable).
#models.py
class Workout(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
date = models.DateField(auto_now_add=True, null=True)
date_created = models.DateTimeField(auto_now_add=True, null=True)
def __str__(self):
return f'{self.date}'
class ReadinessQuestion(models.Model):
name = models.CharField(max_length=40, unique=True)
def __str__(self):
return f'{self.name}'
class WorkoutReadiness(models.Model):
class Rating(models.IntegerChoices):
LOWEST = 1
LOWER = 2
MEDIUM = 3
HIGHER = 4
HIGHEST = 5
workout = models.ForeignKey(Workout, on_delete=models.CASCADE, null=True, blank=True)
readiness_question = models.ForeignKey(ReadinessQuestion, on_delete=models.CASCADE, null=True)
rating = models.IntegerField(choices=Rating.choices)
#forms.py
class WorkoutReadinessForm(forms.ModelForm):
class Meta:
model = WorkoutReadiness
fields = ['readiness_question', 'rating',]
readiness_question = forms.CharField(
widget=forms.TextInput(attrs={'readonly':'readonly'})
)
WRFormSet = forms.formset_factory(WorkoutReadinessForm, extra=0)
#views.py
def create_workoutreadiness(request):
questions = ReadinessQuestion.objects.all()
initial_data = [{'readiness_question':q} for q in questions]
if request.method == 'POST':
formset = WRFormSet(request.POST)
workout = Workout.objects.create(user=request.user)
if formset.is_valid():
for form in formset:
cd = form.cleaned_data
readiness_question = cd.get('readiness_question')
rating = cd.get('rating')
w_readiness = WorkoutReadiness(
workout=workout,
readiness_question=readiness_question,
rating=rating
)
w_readiness.save()
return HttpResponseRedirect(reverse_lazy('routines:workout_exercise_list', kwargs={'pk':workout.id}))
else:
formset = WRFormSet(initial=initial_data)
context = {
'questions':questions,
'formset':formset,
}
return render(request, 'routines/workout_readiness/_form.html', context)
ALTERNATIVELY:
I can drop the 'readonly' attr and change the rendering of my HTML. However, this causes formset.is_valid() to be false, as the initial data field is not populated. screenshot of option 2
#forms.py
class WorkoutReadinessForm(forms.ModelForm):
class Meta:
model = WorkoutReadiness
fields = ['readiness_question', 'rating',]
WRFormSet = forms.formset_factory(WorkoutReadinessForm, extra=0)
#views.py
if request.method == 'POST':
formset = WRFormSet(request.POST, initial=initial_data) ###CHANGED THIS####
workout = Workout.objects.create(user=request.user)
print(formset)
if formset.is_valid():
#form.html
<table>
<form action="" method="post">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
<tr>
<td>{{ form.initial.readiness_question }}</td>
<td>{{ form.rating }}</td>
</tr>
{% endfor %}
<button type="submit" class="btn btn-outline-primary">Submit</button>
</form>
</table>
ALTERNATIVE #2:
Theoretically I could have my WorkoutReadiness model as a wider model with the 5-10 questions as columns, and 1 row per Workout. I know this goes against tidy data modelling principles, but I can't see a way around this in Django at the moment. Formsets are limiting my project.
I managed to solve this (using alternative no.1). I'm not sure it's the best way, but it works for now:
#models.py - Added blank=True to readiness_question in the WorkoutReadiness model
#forms.py - Removed the widget with attrs={'readonly':'readonly'} - I don't think Django supports this anymore, it certainly doesn't encourage it
class WorkoutReadinessForm(forms.ModelForm):
class Meta:
model = WorkoutReadiness
fields = ['readiness_question','rating']
WRFormSet = forms.formset_factory(WorkoutReadinessForm, extra=0)
#views.py - As well as having the formset populated beforehand with the initial_data, I made sure to add it back into each form individually after validating. The form.cleaned_data.readiness_question field is None, until I replace it with each field in initial_data
#CBV
class WorkoutReadinessCreateView(CreateView):
model = WorkoutReadiness
fields = ['readiness_question','rating']
template_name = 'routines/workout_readiness/_form.html'
initial_data = [{'readiness_question':q} for q in ReadinessQuestion.objects.all()]
def post(self, request, *args, **kwargs):
formset = WRFormSet(request.POST)
if formset.is_valid():
return self.form_valid(formset)
return super().post(request, *args, **kwargs)
def form_valid(self, formset):
workout = Workout.objects.create(user=self.request.user)
for idx, form in enumerate(formset):
cd = form.cleaned_data
readiness_question = self.initial_data[idx]['readiness_question']
rating = cd.get('rating')
w_readiness = WorkoutReadiness(
workout=workout,
readiness_question=readiness_question,
rating=rating
)
w_readiness.save()
return HttpResponseRedirect(
reverse_lazy(
'routines:workout_exercise_list',
kwargs={'pk':workout.id})
)
def get_context_data(self, **kwargs):
"""Render initial form"""
context = super().get_context_data(**kwargs)
context['formset'] = WRFormSet(
initial =self.initial_data
)
return context
#FBV
def create_workoutreadiness(request):
questions = ReadinessQuestion.objects.all()
initial_data = [{'readiness_question':q} for q in questions]
if request.method == 'POST':
formset = WRFormSet(request.POST, initial=initial_data)
workout = Workout.objects.create(user=request.user)
if formset.is_valid():
for idx, form in enumerate(formset):
cd = form.cleaned_data
readiness_question = initial_data[idx]['readiness_question']
rating = cd.get('rating')
w_readiness = WorkoutReadiness(
workout=workout,
readiness_question=readiness_question,
rating=rating
)
w_readiness.save()
return HttpResponseRedirect(reverse_lazy('routines:workout_exercise_list', kwargs={'pk':workout.id}))
else:
formset = WRFormSet(initial=initial_data)
context = {
'questions':questions,
'formset':formset,
}
return render(request, 'routines/workout_readiness/_form.html', context)
#html/template - I render form.initial.readiness_question, which means the user can't edit it in a dropdown
<div class="form-group my-5">
<form action="" method="post">
<table class="mb-3">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
<tr>
<td>{{ form.initial.readiness_question }}</td>
<td>{{ form.rating }}</td>
</tr>
{% endfor %}
</table>
<button type="submit" class="btn btn-outline-primary">Submit</button>
</form>
</div>
How it looks - needs tidying up, but the functionality works

How to pass foreign key id to form? or link form to a foriegn key value DJango

I am trying to auto populate my form Student field with the name Carl Park. In short I want my form to use to automatically use the Object ID instead of having to manually select the name Carl Park from the student field drop down menu.
How do I automatically link my Model Form to a foriegn key ID and have the form auto populate based on that value.
Here is my current form, my models.py forms.py views.py and HTML
#models
class Teacher(models.Model):
name = models.CharField(max_length=75)
room = models.CharField(max_length=10)
bio = models.TextField()
class Student(models.Model):
teacher = models.ForeignKey('Teacher', on_delete=models.CASCADE)
name = models.CharField(max_length=75)
class Student_Performance(models.Model):
student = models.ForeignKey('Student', on_delete=models.CASCADE)
behavior_grade = models.CharField(max_length=1)
#form
class StudentPerfromanceForm(ModelForm):
class Meta:
model = Student_Performance
fields = ( "name", "behavior_grade")
#view
def student_details(request, id):
obj = get_object_or_404(Student,pk=id)
form = StudentPerfromanceForm()
if request.method == 'POST':
form = StudentPerfromanceForm(request.POST)
if form.is_valid():
form.save(commit=True)
return all_students_list(request) #pass success message
return render(request, 'class_roster/student_details.html', {'obj':obj, 'reviewForm':form})
#HTML
<form method="POST">
{% csrf_token %}
{{reviewForm.as_p}}
<input type="submit" value="Submit Review">
</form>
I want to more code informations.
anyway if you want set student automatically use object_id,
try
objects.get(query)
ex) ModelClassName.objects.get(pk=1)

create a distinct model option in a dropdown using django

I have created this application but the problem I face now is one that has kept me up all night. I want users to be able to see and select only their own categories when they want to create a post. This is part of my codes and additional codes would be provided on request
category model
class Category(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1,related_name='categories_created')
name = models.CharField(max_length = 120)
slug = models.SlugField(unique= True)
timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
post model
class Post(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1,related_name='posts_created') #blank=True, null=True)
title = models.CharField(max_length = 120)
slug = models.SlugField(unique= True)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='category_created', null= True)
addition codes would be provided immediately on request. Thanks
View.py in post app
def create(request):
if not request.user.is_authenticated():
messages.error(request, "Kindly confirm Your mail")
#or raise Http404
form = PostForm(request.POST or None, request.FILES or None)
user = request.user
categories = Category.objects.filter(category_created__user=user).distinct()
if form.is_valid():
instance = form.save(commit=False)
instance.user = request.user
instance.save()
create_action(request.user, 'Posts', instance)
messages.success(request, "Post created")
return HttpResponseRedirect(instance.get_absolute_url())
context = {
"form": form,
}
template = 'create.html'
return render(request,template,context)
Form
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = [
"title",
"content",
"category",
]
html
{% if form %}
<form method="POST" action="" enctype="multipart/form-data">{% csrf_token %}
{{ form|crispy|safe }}
<input type="submit" name="submit" value="Publish">
</form>
{% endif %}
What you need to do is well-described here. Basically, you are using ModelForm which generates the form from your model. Your model doesn't know anything about filtering by user, so you will need to explicitly add a QuerySet to your form that only shows the desired categories. Change your "categories = ..." line to something like:
form.category.queryset = Category.objects.filter(user=user)
form.fields['category'].queryset = Category.objects.filter(user=user)</strike>

Django Form: Select Multipe allow to add the objects

I have a problem when I want to save the objects such as the Tags, and always returned an error because form validation.
Select a valid choice. hello is not one of the available choices.
Here, I want to implement the select input dynamically which customs additional value from the users creation.
For the frontend demo, like this snippet: https://jsfiddle.net/agaust/p377zxu4/
As conceptually, the tags input provide available tags that already created before... But, the important thing is the Users are allowed to create additional tags what they wants.
1. here is my models.py
#python_2_unicode_compatible
class Tag(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
def __str__(self):
return self.title
class Meta:
verbose_name = _('Detail Tag')
verbose_name_plural = _('Tags')
#python_2_unicode_compatible
class Thread(TimeStampedModel):
title = models.CharField(max_length=200)
....
tags = models.ManyToManyField(
Tag, blank=True, related_name='tags_thread')
2. forms.py
from myapp.models import (Tag, Thread)
class ThreadForm(forms.ModelForm):
description = DraceditorFormField()
tags = forms.ModelMultipleChoiceField(
to_field_name='slug', # set the value to slug field, not pk/id
required=False,
label=_('Additional tags'),
help_text=_('Sparate by comma to add more than once, or select from available tags'),
queryset=Tag.objects.all(),
widget=forms.SelectMultiple(attrs={
'placeholder': _('Additional tags'),
'class': 'ui search fluid dropdown dropdown-add-tags'
})
)
class Meta:
model = Thread
fields = '__all__'
exclude = [
'author', 'topic', 'rating',
'created', 'modified'
]
widgets = {
'title': forms.TextInput(attrs={'placeholder': _('Title')})
}
def clean(self):
# this condition only if the POST data is cleaned, right?
cleaned_data = super(ThreadForm, self).clean()
print(cleaned_data.get('tags')) # return queryset of tags
3. views.py
def save_tagging(post_getlist_tags):
"""
return value list of slugs from the filed of `tags`.
allow to create if the tag is doesn't exist.
this function bassed on slug field.
:param `post_getlist_tags` is request.POST.getlist('tags', [])
"""
cleaned_slug_tags = []
for value in post_getlist_tags:
slug = slugify(value)
if Tag.objects.filter(slug=slug).exists():
cleaned_slug_tags.append(slug)
else:
tag = Tag.objects.create(title=value, slug=slug)
cleaned_slug_tags.append(tag.slug)
return cleaned_slug_tags
#login_required
def thread_new(request, topic_slug):
....
topic = get_object_or_404(Topic, slug=topic_slug)
if request.method == 'POST':
form = ThreadForm(request.POST, instance=Thread())
if form.is_valid():
initial = form.save(commit=False)
initial.author = request.user
initial.topic = topic
# set tagging, this will not being executed because error form validation
initial.tags = save_tagging(request.POST.getlist('tags', []))
initial.save()
form.save()
else:
# forms.errors # goes here..
Let checkout what I have when I typing the additional tags,
<select multiple="multiple" id="id_tags" name="tags" placeholder="Additional tags">
<option value="hello" class="addition">hello</option>
<option value="albacore-tuna" class="addition">albacore-tuna</option>
<option value="amur-leopard" class="addition">amur-leopard</option>
<option value="This other once" class="addition">This other once</option>
</select>
This why I implement my field of tags in the form is like this...
tags = forms.ModelMultipleChoiceField(
to_field_name='slug'
....
)
I would be very appreciated for the answers... :)
Update Solved
Thank you so much for #Resley Rodrigues for help.. Finally, I got it without the form field... only handled in the views and the template.
def save_tagging(post_getlist_tags):
"""
return objects list of tags.
allow to create if the tag is doesn't exist.
this function bassed on slug field.
:param `post_getlist_tags` is request.POST.getlist('fake_tags', [])
"""
cleaned_tags = []
for value in post_getlist_tags:
slug = slugify(value)
if Tag.objects.filter(slug=slug).exists():
tag = Tag.objects.filter(slug=slug).first()
cleaned_tags.append(tag)
else:
# makesure the slug is not empty string.
# because I found the empty string is saved.
if bool(slug.strip()):
tag = Tag.objects.create(title=value, slug=slug)
tag.save()
cleaned_tags.append(tag)
return cleaned_tags
#login_required
def thread_new(request, topic_slug):
....
if request.method == 'POST':
form = ThreadForm(request.POST, instance=Thread())
if form.is_valid():
initial = form.save(commit=False)
initial.author = request.user
....
form.save()
# set tagging after created the object
saved_tags = save_tagging(request.POST.getlist('fake_tags', []))
initial.tags.add(*saved_tags)
and the templates.html using field named by fake_tags, I just think it should hasn't crash with field that already named by tags.
<select name="fake_tags" multiple="multiple" class="ui search fluid dropdown dropdown-add-tags"></select>
<script>
$(document).ready(function() {
$('.ui.dropdown.dropdown-add-tags').dropdown({
placeholder: '{% trans "Additional tags" %}',
allowAdditions: true,
minCharacters: 3,
apiSettings: {
url: 'http://api.semantic-ui.com/tags/{query}'
}
});
});
</script>
For edit mode, add the following these lines below after end-tag of $('.ui.dropdown.dropdown-add-tags').dropdown({...});
thread is instance object.
var items = [{% for tag in thread.tags.all %}"{{ tag.title }}"{% if not forloop.last %},{% endif %}{% endfor %}];
$('.ui.dropdown.dropdown-add-tags').dropdown(
'set selected', items
);

How to display objects in a drop down list (django)?

python=2.7, django=1.11.13
In my html I am not able to display my condition choices from my models.py
When filling the form, the user is not able to choose a condition because they are not displayed.
models.py
class Books(models.Model):
book_name = models.CharField(max_length=100)
book_condition = models.ForeignKey('Condition')
def __unicode__(self):
return self.book_name
class Condition(models.Model):
NEW = 'new'
USED = 'used'
COLLECTIBLE = 'collectible'
CONDITION_CHOICES = (
(NEW, 'New'),
(USED, 'Used'),
(COLLECTIBLE, 'collectible'),
)
book = models.ForeignKey(Books)
condition = models.CharField(max_length=10, choices=CONDITION_CHOICES)
def __unicode__(self):
return self.condition
views.py
def add_book(request):
if request.method == 'GET':
context = {
'form': BookForm()
}
if request.method == 'POST':
form = BookForm(request.POST)
if form.is_valid():
form.save()
context = {
'form': form,
}
return render(request, 'add_book_form.html', context=context)
add_book_form.html
{% extends 'base.html' %}
{% block body %}
<h3>Add Book </h3>
<form action="" method="post">
{% csrf_token %}
{{ form}}
<br/>
<input class="button" type="submit" value="Submit"/>
</form>
{% endblock %}
And this is my form, I'm not sure what I am missing.
form
from django.forms import ModelForm
from .models import Books, Condition
class BookForm(ModelForm):
class Meta:
model = Books
fields = '__all__'
class ConditionForm(ModelForm):
class Meta:
model = Condition
fields = '__all__'
The form you're passing to the view is a BookForm, the BookForm contains a ForeignKey field to the Condition model, so the options in the select will be instances of the Condition model.
You would need to preemptively create the Condition model instances, via the admin interface or the shell, and then you could see the conditions on the select, but that won't help, because your Condition instance needs to be associated to a Book, and that makes me think your software is badly designed.
Let me propose a solution:
class Book(models.Model):
"""
This model stores the book name and the condition in the same
table, no need to create a new table for this data.
"""
NEW = 0
USED = 1
COLLECTIBLE = 2
CONDITION_CHOICES = (
(NEW, 'New'),
(USED, 'Used'),
(COLLECTIBLE, 'Collectible'),
)
name = models.CharField(max_length=100)
condition = models.SmallIntegerField(choices=CONDITION_CHOICES)
def __unicode__(self):
return "{0} ({1})".format(self.book_name, self.condition)
class BookForm(ModelForm):
class Meta:
model = Book
fields = '__all__'
Now the conditions are saved as an integer (like it would be if you used foreign keys) and your software is easier to understand and develop.
Try to use Django widgets. For example:
class BookForm(forms.Form):
categories = (('Adventure', 'Action'),
('Terror', 'Thriller'),
('Business', 'War'),)
description = forms.CharField(max_length=9)
category = forms.ChoiceField(required=False,
widget=forms.Select,
choices=categories)

Categories

Resources