If I had a person model and a person modelform, could I use the model to insert the values into the modelform and check if it is_valid?
Example:
class Person(models.Model:
name = models.CharField(max_length=50)
age = models.IntegerField(default=0)
class PersonModelForm(ModelForm):
model = Person
fields = '__all__'
if request.method == 'POST':
name = request.POST['name']
age = request.POST['age']
person = Person(name=name, age=age)
person_form = PersonModelForm(INSERT HERE SOMETHING)
if person_form.is_valid:
print('person_form is valid')
Yes, you can pass it as the instance kwarg:
person_form = PersonModelForm(instance=person)
But you most often don't need to do that, since the whole point of a ModelForm is that it populates the instance into itself automatically from the POSTed form from the request.
And, even manually doing that is more convenient than what you had before:
if request.method == 'POST':
person_form = PersonModelForm(request.POST)
if person_form.is_valid:
print('person_form is valid')
Related
In one of my recent project, I created a website for users to submit their information in a multi-stage form, in each form I use get_or_create to see if the user submit information previously or not, for example, consider user education model as follows,
class UserEducation(models.Model):
user = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE)
university_name = models.CharField(max_length=100)
in the view, I have the following code,
def education_view(request):
if request.method == "POST":
uedu, created = UserEducation.objects.get_or_create(user=request.user)
uedu.university_name = request.POST['university_name']
uedu.save()
return HttpResponse("success")
I didn't set uploading for the submit button and the problem is some users have multiple education object!
Does anyone know why this happened and whats is wrong with get_or_create?
Insted you can use update_or_create
uedu, created = UserEducation.objects.update_or_create(
user=request.user,uedu.university_name = request.POST['university_name'],
defaults={'user': 'default_value'},
)
I think the reason is that, as every time you moved on to the next step, Django will think that you tell it to create a new object because, as every time you submit a form, a new Model will be created.
What you should do is to halt the process until everything is finished. Something like:
class Person(models.Model):
fn = models.CharField(max_length=40)
class Pet(models.Model):
owner = models.ForeignKey(Person)
name = models.CharField(max_length=40)
class PersonForm(forms.ModelForm):
class Meta:
model = Person
class PetForm(forms.ModelForm):
class Meta:
model = Pet
exclude = ('owner',)
#views
def step1(request):
initial={'fn': request.session.get('fn', None)}
form = PersonForm(request.POST or None, initial=initial)
if request.method == 'POST':
if form.is_valid():
request.session['fn'] = form.cleaned_data['fn']
return HttpResponseRedirect(reverse('step2'))
return render(request, 'step1.html', {'form': form})
def step2(request):
form = PetForm(request.POST or None)
if request.method == 'POST':
if form.is_valid():
pet = form.save(commit=False)
person = Person.objects.create(fn=request.session['fn'])
pet.owner = person
pet.save()
return HttpResponseRedirect(reverse('finished'))
return render(request, 'step2.html', {'form': form})
Reference
I find the following note in the documentation,
Warning
This method is atomic assuming that the database enforces uniqueness
of the keyword arguments (see unique or unique_together). If the
fields used in the keyword arguments do not have a uniqueness
constraint, concurrent calls to this method may result in multiple
rows with the same parameters being inserted.
the university does not have a unique constraint on the user foreign key and as the result, multiple objects will be saved in the concurrent calls.
In send_option (in views) variable I have name of Send.
I would like to have ID of Send
How to do it? Thanks
Form:
class SendOrderForm(forms.Form):
send_option = forms.ModelChoiceField(queryset=Send.objects.all())
Model:
class Send(models.Model):
name = models.CharField(max_length=50)
price = models.DecimalField(max_digits=5, decimal_places=2)
time = models.CharField(max_length=150)
Views:
if request.method == 'POST':
form = SendOrderForm(request.POST)
if form.is_valid():
send_option = form.cleaned_data['send_option']
What you can do is,
if request.method == 'POST':
form = SendOrderForm(request.POST)
if form.is_valid():
send_option = form.cleaned_data['send_option'].id
form.cleaned_data['send_option'] would get the object, and you can get its id by doing a .pk or .id
Does your Send model has __unicode__ or __str__ method that returns name of the instance?
If so, you need to return id of instance rather than name.
I would like to modify a user submitted form to automatically insert the project_id, but I keep getting the error that project_id in the Employee model cannot be null;
My model:
class Project(models.Model):
name = models.CharField(max_length=100)
date_started = models.DateTimeField()
class Employee(models.Model):
name = models.CharField(max_length=200)
project = models.ForeignKey(Project)
class AddEmployeeForm(ModelForm):
class Meta:
model = Employee
exclude = ('project',)
My view:
def emp_add(request, project_id):
if request.method == 'POST':
post = request.POST.copy() # make the POST QueryDict mutable
post('project', project_id)
form = AddEmployeeForm(post)
if form.is_valid():
saved = form.save()
Like this?
if form.is_valid():
employee = form.save(commit=False)
employee.project = Project.objects.get(pk=project_id)
employee.save()
#maciag.artur's answer, to save with commit=False will work. Another way is to instantiate an Employee with the required project_id, and use it to construct the form.
This is useful if your model form's custom clean method relies on the Employee.project field.
def emp_add(request, project_id)
if request.method == 'POST':
# create a new employee with the given project id
employee = Employee(project_id) = project_id
form = AddEmployeeForm(request.POST, instance=employee)
if form.is_valid():
saved = form.save()
<snip>
For reference, see the note box below Using a subset of fields on the form in the Django docs.
Add the project ID to the form as a hidden input. When the request comes back as a POST, it will exist in the POST object, from the form.
def emp_add(request, project_id):
if request.method == 'POST':
post = request.POST.copy() # make the POST QueryDict mutable
post('project', project_id)
form = AddEmployeeForm(post)
if form.is_valid():
saved = form.save()
else:
form = AddEmployeeForm(initial={'project_id':'my_id_value'})
I have a sample form:
class AdminDiscountForm(ModelForm):
class Meta:
model = Discount
exclude = ('company',)
the model it's pointing to is:
class Discount(models.Model):
class Meta:
verbose_name=_('Discount')
verbose_name_plural=_('Discounts')
unique_together = ('company','type')
company = models.ForeignKey(Company)
type = models.CharField(max_length=5, choices=DISCOUNT_CHOICES)
discount = models.DecimalField(max_digits=7, decimal_places=2, verbose_name=_('Discount'))
The form excludes the 'company' field because the user has already selected this using the UI.
i am planning on doing a:
company = blah
if form.is_valid():
obj = form.save(commit=False)
obj.company = company
obj.save()
The problem is that the combination of 'company' and 'type' should be unique (hence the 'unique_together'). This is enforced in the database, so django doesn't care.
I need to extend the clean() method of this form to check for uniqueness as such:
def clean(self):
cleaned_data = self.cleaned_data
# check for uniqueness of 'company' and 'type'
The problem here is that 'company' is not in there because it has been excluded.
What is the best way to raise a form validation error in this case?
-- edit
This is only for adding discount entries.
There's no initial instance.
Jammon's method is the one I use. To expand a bit (using your example):
models.py
class Discount(models.Model):
class Meta:
verbose_name=_('Discount')
verbose_name_plural=_('Discounts')
unique_together = ('company','type')
company = models.ForeignKey(Company)
type = models.CharField(max_length=5, choices=DISCOUNT_CHOICES)
discount = models.DecimalField(max_digits=7, decimal_places=2, verbose_name=_('Discount'))
forms.py
class AdminDiscountForm(ModelForm):
class Meta:
model = Discount
exclude = ('company',)
views.py
def add_discount(request, company_id=None):
company = get_object_or_404(Company, company_id)
discount=Discount(company=company)
if request.method == 'post':
form = AdminDiscountForm(request.POST, instance=discount)
if form.is_valid():
form.save()
return HttpResponse('Success')
else:
form = AdminDiscountForm(instance=company)
context = { 'company':company,
'form':form,}
return render_to_response('add-discount.html', context,
context_instance=RequestContext(request))
This works by creating an instance of your discount model, then binding your form to this instance. This instance is not saved to your db but used to bind the form. This bound form has a value for company of the bound instance. It is then sent to your template for the user to fill out. When the user submits this form, and the form is validated, the model validation check will check for uniqueness of the unique together defined in Meta.
See Model Validation Docs and overriding clean for ModelForms
edit:
You can do a couple of things to catch non unique together entry attempts.
Inside your form.is_valid() you can except an Integrity Error like this:
if request.method == 'post':
form = AdminDiscountForm(request.POST, instance=discount)
if form.is_valid():
try:
form.save()
return HttpResponse('Success')
except IntegrityError:
form._errors["company"] = "some message"
form._errors["type"] = "some message"
else:
...
Use self.instance within the model form's clean method to check for uniqueness.
You could try this:
discount = Discount(company = blah)
form = AdminDiscountForm(request.POST, instance=discount)
if form.is_valid():
discount = form.save()
And the docs say: By default the clean() method validates the uniqueness of fields that are marked as ... unique_together
I want to populate two foreign key fields in one of my forms. The relevant bit of code is as below:
if request.method == 'POST':
form = IssuesForm(request.POST or None)
if request.method == 'POST' and form.is_valid():
form.save()
else:
form = IssuesForm(initial={'vehicle': stock_number, 'addedBy': request.user, })
vehicle points to the Vehicle class. addedBy is to contain the currently logged in user.
However the drop downs aren't initialized as I want...I still have to select the vehicle and user. From this I have two questions:
What could be the problem?
What is the best way to make these forms read-only?
EDIT 1
The IssueForm class looks like this so far:
class Issues(models.Model):
vehicle = models.ForeignKey(Vehicle)
description = models.CharField('Issue Description', max_length=30,)
type = models.CharField(max_length=10, default='Other', choices=ISSUE_CHOICES)
status = models.CharField(max_length=12, default='Pending',
choices=ISSUE_STATUS_CHOICES)
priority = models.IntegerField(default='8', editable=False)
addedBy = models.ForeignKey(User, related_name='added_by')
assignedTo = models.CharField(max_length=30, default='Unassigned')
dateTimeAdded = models.DateTimeField('Added On', default=datetime.today,
editable=False)
def __unicode__(self):
return self.description
Form Class
class IssuesForm(ModelForm):
class Meta:
model = Issues
exclude = ('assignedTo')
For your second question, are you wanting to make the addedBy field read-only? If so, don't add it to your form (it'll never be read-only if you present it to the user, e.g. Firebug). You can instead populate it inside your save method.
if request.method == 'POST':
form = IssuesForm(request.POST or None)
if request.method == 'POST' and form.is_valid():
issue = form.save(commit=False)
issue.addedBy = request.user
# any other read only data goes here
issue.save()
else:
form = IssuesForm(initial={'vehicle': stock_number}) # this is related to your first question, which I'm not sure about until seeing the form code
To make a form read only: on your form class, overwrite the __init__method to disable html fields:
def __init__(self, *args, **kwargs):
super(IssuesForm, self).__init__(*args, **kwargs)
for key in self.fields.keys():
self.fields[key].widget.attrs = {'disabled': 'disabled'}
Makes sure you also don't listen for POST requests, if so, don't save the form.
You can further customize the __init__method to take some arguments and set fields to these values after the super method has been called.