I have searched the existing answers on this platform related to this but it isn't helping me.
I save the Form but it is not saved in the DB.
views.py:
def student_list(request, pk) :
course = get_object_or_404(Course, pk=pk)
teacher = Instructor.objects.get(course_name=course)
attendance = Attendance.objects.create(course_name=course, instructor_name=teacher)
if request.method == "POST" :
form = PostForm(request.POST, instance=attendance)
if form.is_valid() :
attendance = form.save(commit=False)
attendance.published_date = timezone.now()
attendance.save()
return redirect('post_list')
else :
form = PostForm(instance=attendance)
return render(request, 'blog/student_list.html', {'course' : course, 'form' : form})
forms.py:
class PostForm(forms.ModelForm) :
class Meta :
model = Attendance
fields = ('student_name',)
def __init__(self, *args, **kwargs) :
super().__init__(*args, **kwargs)
if self.instance.pk :
self.fields['student_name'].queryset = Student.objects.filter(course_name=self.instance.course_name)
models.py:
class Attendance(models.Model) :
course_name = models.ForeignKey(Course, on_delete=models.SET_NULL, null=True)
student_name = models.ManyToManyField(Student)
instructor_name = models.ForeignKey(Instructor, on_delete=models.SET_NULL, null=True)
published_date = models.DateTimeField(blank=True, null=True)
student_list.html:
<form method="POST" class="post-form">{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-default">Save</button>
</form>
Also, the fields course_name, instructor_name and published_date are saved in the DB. It is only the student_name that is not saved.
Well this is one of the reasons why using .save(commit=False) is indeed not a good idea, because a ModelForm will indeed save the ManyToManyField. This is done after the object itself is saved, since it first needs a primary key.
You can alter the instance wrapped in the form with:
def student_list(request, pk):
course = get_object_or_404(Course, pk=pk)
teacher = Instructor.objects.get(course_name=course)
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
form.instance.course_name = course
form.instance.instructor_name = teacher
form.instance.published_date = timezone.now()
form.save()
return redirect('post_list')
else:
form = PostForm()
return render(request, 'blog/student_list.html', {'course': course, 'form': form})
That being said, if this is the standard behavior, you can just pass the timezone.now as default in your model:
from django.utils import timezone
class Attendance(models.Model):
course_name = models.ForeignKey(Course, on_delete=models.SET_NULL, null=True)
student_name = models.ManyToManyField(Student)
instructor_name = models.ForeignKey(Instructor, on_delete=models.SET_NULL, null=True)
published_date = models.DateTimeField(blank=True, null=True, default=timezone.now)
or even better, you can just use auto_now_add=True [Django-doc]:
class Attendance(models.Model):
course_name = models.ForeignKey(Course, on_delete=models.SET_NULL, null=True)
student_name = models.ManyToManyField(Student)
instructor_name = models.ForeignKey(Instructor, on_delete=models.SET_NULL, null=True)
published_date = models.DateTimeField(auto_now_add=True)
Note: A ForeignKey does not store the string representation (or name) of the
referenced object in the column, it stores the primary key of the record it
references in a column with an _id suffix to a ForeignKey field. Therefore
ForeignKeys usually do not end with a _name suffix. You might want to
consider renaming the course_name field to course.
Note: A GET request is not supposed to have side-effects, hence constructing
objects when a user makes a GET request, is not compliant with the HTTP
standard. Therefore it might be better to remove the creation of the Attendance
object at the database side.
EDIT: You can pass the course object to the PostForm to filter the queryset. For example:
class PostForm(forms.ModelForm) :
class Meta :
model = Attendance
fields = ('student_name',)
def __init__(self, *args, course=None, **kwargs) :
super().__init__(*args, **kwargs)
if self.instance.pk and not course:
course = self.instance.course_name
if course:
self.fields['student_name'].queryset = Student.objects.filter(course_name=course)
Then in the view, you can pass the Course object:
def student_list(request, pk) :
course = get_object_or_404(Course, pk=pk)
teacher = Instructor.objects.get(course_name=course)
if request.method == 'POST':
form = PostForm(request.POST, course=course)
if form.is_valid() :
form.instance.course_name = course
form.instance.instructor_name = teacher
form.instance.published_date = timezone.now()
form.save()
return redirect('post_list')
else :
form = PostForm(course=course)
return render(request, 'blog/student_list.html', {'course' : course, 'form' : form})
Related
I have a model that holds an inventory value. I want to be able to add any integer value to that model field. It doesn't throw any errors but I get unexpected results when I post. For some reason I do not understand, the code is multiplying the number entered in the form by 2 and setting the result as the new inventory number. I have tried using both forms.Form and ModelForm for my form. The form I am using is also used to update the Panel description, so the text area for the description is pre-populated with the current Panel description. I'm not sure if it's important but the form input for the inventory is also pre-populated with the current inventory number. I didn't think this was a big deal as long as you delete it and type the actual amount of inventory you wanted to add.
For example, if I have an inventory of 80 and I enter 3 in the input field and POST, the new inventory will be 6. That is obviously not what I expect to happen, I would like it to POST and make the new inventory 83.
Models.py
class Panel(models.Model):
name = models.CharField('Name', max_length=50, default='')
slug = models.SlugField(unique=True)
description = models.TextField('Description', null=True, blank=True)
date_created = models.DateTimeField('Date Created', auto_now_add=True, null=True)
display = models.BooleanField(default=True)
image = models.ImageField('Panel Image', null=True, blank=True)
inventory = models.IntegerField('Inventory', null=True, default=0)
def get_absolute_url(self):
return reverse("model_detail", kwargs={"pk": self.pk})
def __str__(self):
return f'{self.name}'
def save(self, *args, **kwargs):
if not self.slug or self.slug != slugify(self.name):
self.slug = slugify(self.name)
return super().save(*args, **kwargs)
Forms.py
class UpdatePanelForm(ModelForm):
class Meta:
model = Panel
fields = ['description', 'inventory']
Views.py
def panel_edit(request, slug):
panel = Panel.objects.get(slug=slug)
form = UpdatePanelForm(instance=panel)
if request.method == 'POST':
form = UpdatePanelForm(request.POST, instance=panel)
if form.is_valid():
panel.description = form.cleaned_data.get('description')
current_inventory = panel.inventory
form_input = form.cleaned_data.get('inventory')
new_inventory = current_inventory + form_input
panel.inventory = new_inventory
panel.save()
return redirect('panels')
context = {'form': form, 'panel': panel}
return render(request, 'main/panel_edit.html', context)
I have also tried the below approach (probably the nicer way of writing it) and it also multiplies the number by 2.
add_number = form.cleaned_data.get('inventory')
panel.inventory += add_number
html
<form method='POST'>
{% csrf_token %}
<h3>{{panel.name}}</h3><br>
<h4>Panel Description</h4>
{{form.description}}<br>
<h4>Panel Inventory</h4>
<h4>Current Inventory: </h4><h2>{{panel.inventory}}</h2><br>
<h4>Add inventory below</h4><br>
{{form.inventory}}<br><br>
<input type="submit" value="Submit">
</form>
Found the answer by debugging my own code. Turns out I need to initialize current_inventory above the if statements. I'll just use JS to change the initial value of the input field.
def panel_edit(request, slug):
panel = Panel.objects.get(slug=slug)
current_inventory = panel.inventory
form = UpdatePanelForm(instance=panel)
if request.method == 'POST':
form = UpdatePanelForm(request.POST, instance=panel)
if form.is_valid():
panel.description = form.cleaned_data.get('description')
form_input = form.cleaned_data.get('inventory')
new_inventory = current_inventory + form_input
panel.inventory = new_inventory
panel.save()
return redirect('panels')
context = {'form': form, 'panel': panel}
return render(request, 'main/panel_edit.html', context)
I have several forms that take people through steps and below are the first two and the simplest ones and makes it easy to explain what i am having problem with.
The following two views are login required and contain one form on each. First view is the new_operator where the user fills out a single text input field. Second view is the new_asset where the user fills one text input field as the asset name and selects an operator from the a select/dropdown field. The question is how can i get the form to remember the operator name the user created in the previous form and make it as the default option? To be clear, i still want the user to select any other operator if they choose to do so but i want the option they just created to be the default. Thanks a lot in advance for the help.
First, here are the models:
class OperatorCompany(models.Model):
name = models.CharField(max_length=50, unique=True)
created_at = models.DateTimeField(default=timezone.now)
created_by = models.ForeignKey(User, related_name='operator_added_by', null=True, on_delete=models.SET_NULL)
class Meta:
verbose_name = "Operator Company"
verbose_name_plural = "Operator Companies"
def __str__(self):
return self.name
class AssetName(models.Model):
name = models.CharField(max_length=50, unique=True)
operator = models.ForeignKey(OperatorCompany, related_name='asset', on_delete=models.CASCADE)
created_at = models.DateTimeField(default=timezone.now)
created_by = models.ForeignKey(User, related_name='asset_added_by', null=True,
on_delete=models.SET_NULL)
class Meta:
verbose_name = "Asset"
verbose_name_plural = "Assets"
def __str__(self):
return self.name
views.py
def new_operator(request):
if request.method == 'POST':
form = NewOperatorForm(request.POST)
if form.is_valid():
newoperator = form.save(commit=False)
newoperator.created_by = request.user
newoperator.created_at = timezone.now()
newoperator.save()
return redirect('wellsurfer:new_asset')
else:
form = NewOperatorForm()
return render(request, 'wellsurfer/create_new_operator.html', {'create_operator': form})
def new_asset(request):
if request.method == 'POST':
form = NewAssetForm(request.POST)
if form.is_valid():
newasset = form.save(commit=False)
newasset.created_by = request.user
newasset.created_at = timezone.now()
newasset.save()
return redirect('wellsurfer:new_pad')
else:
form = NewAssetForm()
return render(request, 'wellsurfer/create_new_asset.html', {'create_asset': form})
and following are the forms.py without the init, clean functions and the widgets
class NewOperatorForm(forms.ModelForm):
class Meta:
model = OperatorCompany
fields = ('name',)
class NewAssetForm(forms.ModelForm):
class Meta:
model = AssetName
fields = ('name', 'operator')
To share data between multiple pages, you can use session variables. These are stored on the server and associated to clients according to the session cookie they communicate to the server at every request.
Typically, in the first view, you would add after save():
request.session['latest_created_operator_id'] = newoperator.id
to save in the session the operator id.
And in the second view, after the else,
operator_id = request.session.get('latest_created_operator_id', None)
operator = Operator.objects.filter(id=operator_id).first() # returns None if not found
form = NewAssetForm(initial={'operator': operator})
retrieves the operator and populates the form.
(That's untested code; you may need to edit a bit.)
At a glance, maybe something like this would work.
What you can do is add another URL in urls.py for new_asset which accepts a OperatorCompany id. I don't have your url config but it could be something like:
urls.py
path('wellsurfer/new_asset/<int:operator_id>', new_asset, name='wellsurfer:new_asset_operator')
view.py
def new_operator(request):
if request.method == 'POST':
form = NewOperatorForm(request.POST)
if form.is_valid():
newoperator = form.save(commit=False)
newoperator.created_by = request.user
newoperator.created_at = timezone.now()
newoperator.save()
return redirect('wellsurfer:new_asset', operator_id=newoperator.id)
else:
form = NewOperatorForm()
return render(request, 'wellsurfer/create_new_operator.html', {'create_operator': form})
def new_asset(request, operator_id=None):
if request.method == 'POST':
form = NewAssetForm(request.POST)
if form.is_valid():
newasset = form.save(commit=False)
newasset.created_by = request.user
newasset.created_at = timezone.now()
newasset.save()
return redirect('wellsurfer:new_pad')
else:
form = NewAssetForm()
if operator_id is not None:
operator_company = OperatorCompany.objects.get(pk=operator_id)
form.fields['operator'].initial = operator_company
return render(request, 'wellsurfer/create_new_asset.html', {'create_asset': form})
I am trying to make use of model formset in Django. Howwever, My Model has a foreignkey which I want to make use of request.user in the form to assist me in tracking the person that save the information. I am getting this error.
#jobseeker_required
def add_skills(request):
template_name = 'jobseeker/addskill.html'
heading_message = 'Formset Demo'
SkillFormSet = modelformset_factory(JobSeekerSkills, fields=('skill', 'level',))
form = SkillFormSet()
if request.method == 'POST':
form = SkillFormSet(request.POST)
a = form.save(commit=False)
a.jobseeker = request.user.id
a.save()
return render(request, template_name, {'form': form})
class JobSeekerSkills(models.Model):
LEVEL = (
('Beginner', 'Beginner' ),
('Intermediary', 'Intermediary'),
('Advance', 'Advance'),
)
jobseeker = models.ForeignKey(User, on_delete=models.CASCADE)
skill = models.CharField(max_length=255)
level = models.CharField(max_length=25, blank=True, null=True, choices=LEVEL, default='Beginer')
updated_at = models.DateTimeField(auto_now=True)
created_at = models.DateTimeField(auto_now_add=True)
modelformset_factory returns a formset type so SkillFormSet is a formset type, not a form. Instantiating it will return a formset instance. Also, formset.save returns a list of form instances, not a single form -- this is the source of the error you're getting.
You need to iterate over the list and add the attribute:
formset = SkillFormSet(request.POST)
forms = formset.save(commit=False)
for form in forms:
form.jobseeker = request.user.id
form.save()
I have a "product" field that I want to use to determine which form to display. I am trying to do this in the view but wondering if I should do it in the template instead. I have tried the following but "form" does not get assigned by my if statements. What am I doing wrong?
#login_required
def update_message(request, pk):
message = get_object_or_404(Submission, pk=pk)
author = message.author
date_posted = message.date_posted
product = message.product
message_obj = Submission.objects.get(pk=pk)
program_type = message.program_type
if author == request.user:
if request.method == 'POST':
if product == 'Apple':
form = AppleForm(request.user, request.POST, instance=message)
if product == 'Orange':
form = OrangeForm(request.user, request.POST, instance=message)
if form.is_valid():
message_sub = form.save(commit=False)
message_sub.author = request.user
message_sub.date_posted = timezone.now()
message_sub.save()
form.save_m2m()
messages.success(request, 'Message updated')
return redirect('submission-list')
else:
if product == 'Apple':
form = AppleForm(request.user, instance=message)
if product == 'Orange':
form = OrangeForm(request.user, instance=message)
else:
messages.warning(request, 'You can't do that.')
return redirect('message-submission-list')
return render(request, 'programs/submission_create_form.html', {'product':product,'form': form, 'message_obj': message_obj,'program_type':program_type})
class MessageSubmission(models.Model):
message = models.CharField(max_length=5000)
author = models.ForeignKey(User, on_delete=models.CASCADE)
date_posted = models.DateTimeField(default=timezone.now)
program_code = models.ManyToManyField(Program)
program_type = models.CharField(max_length=30, blank=True)
product = models.ForeignKey('Product', on_delete=models.SET_NULL, null=True)
production_cycle = models.ManyToManyField('ProductionCycle', null=True)
def get_absolute_url(self):
return reverse('submission-list')
def __str__(self):
return self.message
As I mentioned in the comment, the issue is that product is a ForeignKey to another model. In the template, the FK will display using the __str__ method of that model, but that doesn't make it equal to that display value. You should compare explicitly with the relevant field on the target model:
if product.fruit_type == 'Orange' # or whatever the field is
(Alternatively you could do if str(product) == 'Orange' but that's more brittle and is coupling display logic in a way that's not very nice.)
There's nothing wrong with doing this in the views. If the form is not defined after those if statements then it means that the value of product is not Apple or Orange, but something else. I would double check the value of product to fix the issue.
Since Product is a class, you should reference a field. You didn't post the code for it, but for example
if form == product.name
If there is a name field.
I have a form from my model that needs to be validated and saved making use of ManyToMany Fields.
Everytime I try and save it, I get thrown back to the page, just saying this field is required
My models.py
class HuntingReport(models.Model):
user = models.ForeignKey(User, related_name='User')
outfitter = models.ForeignKey(User, related_name='Outfitter', null=True, blank=True)
date_travel_started = models.DateField(blank=True, null=True)
date_travel_ended = models.DateField(null=True, blank=True)
title = models.CharField(max_length=50)
report = models.TextField()
wish_list = models.ManyToManyField(Specie)
bag_list = models.ManyToManyField(Trophies)
def __unicode__(self):
return self.title
My forms.py looks as follows
class HuntingReportForm(ModelForm):
date_travel_started = forms.DateField(widget=extras.SelectDateWidget(years=range(1970,2010)))
date_travel_ended = forms.DateField(widget=extras.SelectDateWidget(years=range(1970,2010)))
wish_list = forms.ModelMultipleChoiceField(queryset=Specie.objects.all(), widget=FilteredSelectMultiple("verbose name", is_stacked=False))
bag_list = forms.ModelMultipleChoiceField(queryset=Trophies.objects.all(), widget=FilteredSelectMultiple("verbose name", is_stacked=False))
class Meta:
model = HuntingReport
exclude = ['user']
def __init__(self, user, *args, **kwargs):
super(HuntingReportForm, self).__init__(*args, **kwargs)
users = User.objects.filter(userprofile__outfitter=True)
self.fields['outfitter'].choices = [('', '')] + [(user.pk, user.get_full_name()) for user in users]
my views.py
def create(request, template_name='reports/new.html'):
if request.method == 'POST':
form = HuntingReportForm(request.POST, request.FILES)
if form.is_valid():
newform = form.save(commit=False)
newform.user = request.user
newform.save_m2m()
return HttpResponseRedirect('/hunting-reports/')
else:
form = HuntingReportForm(request.user)
context = { 'form':form, }
return render_to_response(template_name, context,
context_instance=RequestContext(request))
Did you try passing blank=True for model field's constructor, or required=False for the ModelMultipleChoiceField's constructor?
I know that blank=True solves the problem for the form in the admin panel, but I don't know how it gets mapped to the ModelForm's fields. I'm assuming that it gets mapped to required property.