Substituting an absent field value when saving a modelform - django

I have a this model:
class Fleet(models.Model):
company = models.ForeignKey("Company", editable=False)
aircraft = models.ForeignKey("Aircraft")
size = models.IntegerField(default=1)
description = models.TextField(blank=True)
def __unicode__(self):
return u"%s" % (self.aircraft, )
And then a form based on this model:
class FleetForm(ModelForm):
class Meta:
model = Fleet
exclude = ('company', )
When I use this form in a template, the "company" field is not added, which is expected. But that field is required as blank != True.
The way I use this form, the company attribute will always be known in the view function, as it's passed from the URL. How can I add the company to the form in the view function before I save it?
Here is my view:
def new_fleet(request, company_id):
from forms import FleetForm
company = Company.objects.get(pk=company_id)
if request.method == "POST":
form = FleetForm(request.POST,)
form.company = company #doesn't work
form = form.save(commit=False) #can't do this because the form
form.company = company #doesn't validate (company is not set)
if not form.errors:
form.save()
else:
fleet = Fleet(company=company) #pointless because the company widget
form = FleetForm(instance=fleet) #isn't created but eh whatever

There are two ways to solve this issue:
Instantiate your model with initial values for the missing, but required fields:
company = Company.objects.get(pk=company_id)
fleet = Fleet(company=company)
form = FleetForm(request.POST, instance=fleet)
new_fleet = form.save()
Use save(commit=False) and manually set any extra required fields:
company = Company.objects.get(pk=company_id)
form = FleetForm(request.POST)
fleet = form.save(commit=False)
fleet.company = company
new_fleet = fleet.save()
See the note in this section of the ModelForm API documentation for more details.
By the way, either editable=False or exclude is enough to remove a field from a ModelForm; you don't need both.

in #Ayman Hourieh 's answer . Just to address a pitfall in Django. If you have many-to-many field in the form. it would not get saved here. You should explicitly call save_m2m() . add one more line as below.
form.save_m2m()

Related

"user" is not set when parsing a data-dict to a Django form, but all other fields are

Say I have the following
#models.py
class MyModel(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete = models.CASCADE,null=True)
link = models.URLField(max_length=2048)
date_added = models.DateTimeField(default = timezone.now)
used = models.BooleanField(default = True)
and my form
class MyModelForm(forms.ModelForm):
class Meta:
model = Wishlist
exclude = [
"user","date_added"
]
If I try to create an instance like (note, I have omitted the if request.method="POST" etc. for clarity)
def myView(request):
user = request.user
instance = MyModel(user=user)
data = {"link":link,"date_added":domain,"user":user}
form = MyModelForm(data=data)
form.save()
the link and date_added is written to the database, but the user field is empty.
If I set the user to the instance after the form is created like
def myView(request):
user = request.user
instance = MyModel(user=user)
data = {"link":link,"date_added":domain}
form = MyModelForm(data=data)
form.save(commit=False)
form.instance.user = user #Setting the user here
form.save()
It works like a charm.
How come, I cannot set the user in the data-dict, and is there a way to do so?
How come, I cannot set the user in the data-dict.
A form will ignore all fields that it does not have to process. This makes the webserver more secure: here we ensure that the user can not create a MyModel for another user.
If thus a field belongs to the excluded attribute, or not in the fields attribute from the Meta class, then the form will not set the corresponding model object attribute.
You did not specify the instance to the form:
def myView(request):
user = request.user
instance = MyModel(user=user)
data = {"link":link,"date_added":domain}
form = MyModelForm(data=data, instance=instance)
form.save()

Creating modelform to exclude a field loses "nice" handling of unique contraint

I have a model which includes a 'customer' (fk to User), that I have excluded from a custom form (because the logged in user is already known).
The issue I'm having is that I lose the 'nice' form handling for unique constraints once I override the form_valid method to accomodate the excluded field.
models.py:
class RequestedData(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
station = models.ForeignKey(Station, on_delete=models.CASCADE, verbose_name = 'Measurement Station')
measurement = models.ForeignKey(Measurement, on_delete=models.CASCADE)
class Meta:
verbose_name_plural = "Requested Data"
constraints = [
models.UniqueConstraint(
fields=["customer", "station", "measurement"],
name="station-measurement-unique",)]
The class view is:
class RequestedDataCreate(LoginRequiredMixin, CreateView):
model = RequestedData
# use overriden form to remove customer from selection
form_class = RequestedDataForm
success_url = reverse_lazy("requesteddata_list")
# overwrite form_valid function to add back in user.
def form_valid(self, form):
form.instance.customer = Customer.objects.get(user__username=self.request.user)
return super().form_valid(form)
where you can see I've created a RequestedDataForm to exclude the customer from the form.
class RequestedDataForm(ModelForm):
class Meta:
model = RequestedData
exclude = ("customer",)
All works well, except when the user enters data in the form that violates the unique constraint.
Before I created the custom form to exclude user (i.e. just using the CreateView form), upon violation of the unique constraint a message would appear as shown below, namely, a very neat label in the html that pops up a message.
After creating my own form, what now happens is the user gets taken to an ugly error.
So ultimately, I would like to bring through the same default handling of constraints into my form, but just want to exclude the customer field from the form, given they're logged in so they basically can only select themselves!
You are setting form.instance.customer after form has been validated and marked as valid - hence you don't get nice errors about unique failing. You should set customer in your form before validation.
class RequestedDataCreate(LoginRequiredMixin, CreateView):
model = RequestedData
# use overriden form to remove customer from selection
form_class = RequestedDataForm
success_url = reverse_lazy("requesteddata_list")
def get_initial(self):
initials = super().get_initial()
initials['customer'] = Customer.objects.get(user__username=self.request.user)
return initials
This will set initial customer as the one you want and provide it to form as part of it's kwargs, on form initialization.
def form_valid(self, form):
customer = Customer.objects.get(user=self.request.user)
form.instance.customer = customer
rendered = render_to_string('render.html')
response = HttpResponse(rendered)
try:
valid_data = super(RequestedDataCreate, self).form_valid(form)
except IntegrityError as e:
return response
return valid_data

Django form not updating existing record even when instance is passed

I have a form that is used to create and edit a model instance. But when editing the model instance, the form still tries to create a new record and fails because the unique together fields already exist. I am already passing the instance when initializing the form.
views.py
def organization_course_detail(request, org_id):
'''
Get all courses that are associated with an organization
'''
template_name = 'users/organization-course-list.html'
organization = models.Organization.objects.get(id=org_id)
if request.method == 'POST':
print organization.id
form = forms.CreateOrganizationForm(request.POST, instance=organization)
if form.is_valid():
print 'form is valid'
org = form.save(commit=False)
org.save()
return HttpResponseRedirect(
reverse('users:org-course-list',
kwargs={'org_id': org_id}))
else:
form = forms.CreateOrganizationForm(instance=organization)
forms.py
class CreateOrganizationForm(forms.ModelForm):
'''
A form used to create a new organization. At the same time,
we create a new course that is a clone of "Chalk Talk SAT"
and associate the course with the organization and any student
that signs up from that organization
'''
class Meta:
model = models.Organization
fields = ['name', 'country', 'acronym',]
models.py
class Organization(models.Model):
'''
This is a model where we will have every institution
(test prep centers, governments, schools) that we do workshops
at or create school accounts for
'''
name = models.CharField(max_length=2000)
country = CountryField(null=True, blank='(Select Country)')
date = models.DateTimeField(default=timezone.now)
acronym = models.CharField(max_length=7, help_text="(Up to 7 characters)")
expiration_date = models.DateTimeField(default=timezone.now)
def get_org_admins(self):
return self.admin_for_organizations.all()
class Meta:
unique_together = (
('name', 'country')
)
def __str__(self):
return self.name
Have you looked at the entries in your db table? (or can you post them?)
You might try using the django shell and try to execute your code by hand (query the db with an org_id and check the result. Edit a field and save it. Does that work?)
Also I think blank can only be True or False here.
country = CountryField(null=True, blank='(Select Country)')
https://docs.djangoproject.com/en/1.11/ref/models/fields/#django.db.models.Field.blank

Django IntegrityError signup_simplesubscriber.date_created may not be NULL

I've read every "InterityError" + "may no be NULL" post and still can't track down what's causing this error.
I've got a two-part signup form. First part is just selecting a product. That passes a product ID to the next page as part of the URL, where they input personal info. I can get the form to work fine until I start removing fields -- i'm using model forms -- because some fields don't need to be displayed.
Here's my model, and the modelForm:
class SimpleSubscriber(models.Model):
name = models.CharField(max_length=255)
address = models.CharField(max_length=200)
city = models.CharField(max_length=100)
state = models.CharField(max_length=2)
zipcode = models.CharField(max_length=9)
phone = models.CharField(max_length=10)
email = models.EmailField()
date_created = models.DateTimeField(null=True)
sub_type = models.ForeignKey(Product)
def __unicode__(self):
return self.name
class SubscriberForm(ModelForm):
class Meta:
model = SimpleSubscriber
fields = ('name', 'address', 'city', 'state', 'zipcode', 'phone', 'email', 'sub_type',)#'date_created',
And here's my views:
def select_product(request):
title = "get yourself an e-edition. wurd."
pform = Product.objects.order_by('product_active')
if request.method == 'POST': # If the form has been submitted...
pform = ProductForm(request.POST) # A form bound to the POST data
if pform.is_valid(): # All validation rules pass
# ...
return HttpResponseRedirect('signup/%i' % pform.id) # Redirect after POST
else:
form = ProductForm() # An unbound form
return render_to_response('signup/index.html', {'title': title, 'pform': pform}, context_instance=RequestContext(request))
def subscriber_signup(request, product_id):
productchoice = Product.objects.get(id=product_id)
now = datetime.datetime.now()
title = "We need some information."
if request.method == 'POST': # If the form has been submitted...
sform = SubscriberForm(request.POST) # A form bound to the POST data
if sform.is_valid(): # All validation rules pass
sform.date_created = now
sform.sub_type = productchoice
sform.save()
return HttpResponseRedirect('thankyou/') # Redirect after POST
else:
sform = SubscriberForm() # An unbound form
return render_to_response('signup/detail.html', {'title': title, 'sform': sform, 'productchoice': productchoice, 'now': now.date(),}, context_instance=RequestContext(request))
I think it has something to do with the modelForm, but I'm pretty new, so I really have no idea. If I add all the fields to SubscriberForm, then they get filled out and everything works fine. But I don't want users to have to say when they filled out the form, so i put sform.date_created = now and I want the product_id to be filled in automatically by what choice they picked on the previous page. but if I exclude these fields from the form it throws the IntegrityError, which isn't very helpful in explaining what to change.
Any hints on where I'm messing up?
Thanks,
Two things:
1) You may benefit from using exlude in your form definition:
class SubscriberForm(ModelForm):
class Meta:
model = SimpleSubscriber
exclude = ('date_created', )
2) To your question, heres how to fix it:
if sform.is_valid(): # All validation rules pass
suscriber = sform.save(commit=False)
suscriber.date_created = now
suscriber.sub_type = productchoice
suscriber.save()
Alternatively to #fceruti's suggestion, you can also add more kwarg tags null=True on the model's field where appropriate - only forcing a minimal set of fields to be completed in the form.

Django Modelform (with excluded field)

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