I get an error "NOT NULL constraint failed" when I try to save a formset in an update view and the formset has had new forms added. I think the reason is that the database has a required field (journal_entry) that isn't part of the Formset ModelForm. So when the formset is attempted to be saved lineitem_formset.save() I get the error.
How can I add this required field value before saving the formset?
View.py
#login_required
def entries_update(request, pk):
journal_entry = get_object_or_404(JournalEntry, pk=pk)
journal_entry.date = journal_entry.date.strftime('%Y-%m-%d') #Convert date format to be suitable for Datepicker input.
journal_entry_form = JournalEntryForm(instance=journal_entry)
LineItemFormSet = modelformset_factory(LineItem, fields=('ledger','description','project','cr','dr'), extra=2)
line_items = LineItem.objects.filter(journal_entry=journal_entry)
lineitem_formset = LineItemFormSet(queryset=line_items)
if request.method == 'POST':
lineitem_formset = LineItemFormSet(request.POST)
journal_entry_form = JournalEntryForm(request.POST, instance=journal_entry)
if lineitem_formset.is_valid() and journal_entry_form.is_valid:
lineitem_formset.save() <-- ERROR HAPPENS HERE
journal_entry_form.save()
messages.success(request, "Journal entry successfully updated.")
return HttpResponseRedirect(reverse('journal:entries_show_detail', kwargs={'pk': journal_entry.id}) )
Models.py
class JournalEntry(models.Model):
# User needs to be set back to compulsory !!!
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, null=True, blank=True)
date = models.DateField(null=False, blank=False)
TYPE = (
('BP', 'Bank Payment'),
('YE', 'Year End'),
('JE', 'Journal Entry')
)
type = models.CharField(
max_length=2,
choices=TYPE,
blank=True,
default='0'
)
description = models.CharField(max_length=255, null=True, blank=True)
def __str__(self):
if self.description:
return self.description
else:
return 'Journal Entry' + str(self.id)
class Meta(object):
ordering = ['id']
verbose_name_plural = 'Journal entries'
class LineItem(models.Model):
journal_entry = models.ForeignKey(JournalEntry, on_delete=models.CASCADE) <--- This is the field that needs to be set.
ledger = models.ForeignKey(Ledger, on_delete=models.PROTECT)
description = models.CharField(max_length=255, null=True, blank=True)
project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True, blank=True)
cr = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
dr = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
STATUS = (
('0', 'Not reconciled'),
('1', 'Draft'),
)
status = models.CharField(
max_length=1,
choices=STATUS,
default='0'
)
reconciliation_date = models.DateField(null=True, blank=True)
#def __str__(self):
# return self.description
class Meta(object):
ordering = ['id']
forms.py
class JournalEntryForm(ModelForm):
def clean_date(self):
data = self.cleaned_data['date']
#Check date is not more than 30d future
if data > (datetime.date.today() + datetime.timedelta(30)):
raise ValidationError('Date cannot be more than 30d future')
if data < (datetime.date.today() - datetime.timedelta(90)):
raise ValidationError('Date cannot be more than 90d past')
return data
class Meta:
model = JournalEntry
fields = ['date','description']
widgets = {'date': DateTypeInput()}
class LineItemForm(ModelForm):
class Meta:
model = LineItem
fields = ['ledger','description','project','cr','dr']
# This init disallows empty formsets
def __init__(self, *arg, **kwarg):
super(LineItemForm, self).__init__(*arg, **kwarg)
self.empty_permitted = False
def clean(self):
cr = self.cleaned_data['cr']
dr = self.cleaned_data['dr']
if cr == None and dr == None:
raise ValidationError('You must enter a CR or DR.')
if cr and dr:
raise ValidationError('You must enter a CR or DR, not both.')
Thanks to #Iain Shelvington again. Edited the following lines to use inline_formset and now works:
LineItemFormSet = inlineformset_factory(JournalEntry, LineItem, fields=('ledger','description','project','cr','dr'), extra=2)
and:
lineitem_formset = LineItemFormSet(request.POST, instance=journal_entry)
Related
I asked a question and, as I have read a little, I now can better express what I need:
How to do model level custom field validation in django?
I have this model:
class StudentIelts(Model):
SCORE_CHOICES = [(i/2, i/2) for i in range(0, 19)]
student = OneToOneField(Student, on_delete=CASCADE)
has_ielts = BooleanField(default=False,)
ielts_listening = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
ielts_reading = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
ielts_writing = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
ielts_speaking = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
and have this model form:
class StudentIeltsForm(ModelForm):
class Meta:
model = StudentIelts
exclude = ('student')
def clean(self):
cleaned_data = super().clean()
has_ielts = cleaned_data.get("has_ielts")
if has_ielts:
msg = "Please enter your score."
for field in self.fields:
if not self.cleaned_data.get(str(field)):
self.add_error(str(field), msg)
else:
for field in self.fields:
self.cleaned_data[str(field)] = None
self.cleaned_data['has_ielts'] = False
return cleaned_data
What I am doing here is that checking if has_ielts is True, then all other fields should be filled. If has_ielts is True and even one field is not filled, I get an error. If has_ielts is False, an object with has_ielts=False and all other fields Null should be saved. I now want to do it on the model level:
class StudentIelts(Model):
SCORE_CHOICES = [(i/2, i/2) for i in range(0, 19)]
student = OneToOneField(Student, on_delete=CASCADE)
has_ielts = BooleanField(default=False,)
ielts_listening = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
ielts_reading = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
ielts_writing = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
ielts_speaking = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
def clean(self):
# I do not know what to write in here
and have this model form:
class StudentIeltsForm(ModelForm):
class Meta:
model = StudentIelts
exclude = ('student')
In the clean method of my model I want something with this logic(this is psedue code):
def clean(self):
msg = "Please enter your score."
if self.has_ielts:
my_dic = {}
for f in model_fields:
if f is None:
my_dic.update{str(field_name): msg}
raise ValidationError(my_dic)
How can I do this?
How can I get the same result as my modelform but at the model level?
You need to explicitly declare the fields that should be non-empty, otherwise you're cycling through fields that are not related to your clean method, like id and student. Someone might want to add a field later one that's not mandatory and wonder why it raises a validation error.
class StudentIelts(Model):
# fields
non_empty_fields = ['ielts_reading', ...]
def clean(self):
errors = {}
if self.has_ielts:
for field_name in self.non_empty_fields:
if not getattr(self, field_name):
errors[field_name] = msg
if errors:
raise ValidationError(errors)
When requesting a blank RegisterForm, I get a KeyError. I believe that when I click the link on the site, I submit a GET request to the register method and the form1 = RegisterForm() line fires. I believe there is something wrong with my RegisterForm class but I can't figure out what it is. I'm using the User model and UserCreationForm. Any help is greatly appreciated.
KeyError at /profile/pm/register u'username'
Traceback:
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/exception.py" in inner
39. response = get_response(request)
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py" in _legacy_get_response
249. response = self._get_response(request)
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py" in _get_response
187. response = self.process_exception_by_middleware(e, request)
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py" in _get_response
185. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/vagrant/project/tmp/user_profile/views.py" in register
77. form1 = RegisterForm()
File "/usr/local/lib/python2.7/dist-packages/django/contrib/auth/forms.py" in __init__
97. self.fields[self._meta.model.USERNAME_FIELD].widget.attrs.update({'autofocus': ''})
Exception Type: KeyError at /profile/pm/register
Exception Value: u'username'
views.py
def register(request, user_type):
if user_type not in ["pm", "hm"]:
raise Http404
if request.user.is_authenticated():
return redirect(reverse('index'))
if user_type == 'hm':
userTypeChoices = [Profile.RecruiterAgency, Profile.RecruiterInternal, Profile.HiringManager]
else:
userTypeChoices = [Profile.PMEmployee, Profile.PMContractConsultant, Profile.PMBoth]
if request.method =="POST":
# accept tos
if 'accepted_tos' in request.POST:
return acceptedTOS(request)
form1 = RegisterForm(request.POST)
form2 = ProfileForm(request.POST)
if form1.is_valid() and form2.is_valid():
user = form1.save()
profile = Profile()
profile.user_id = user.id
profile.company = form2.cleaned_data["company"]
profile.phone = form2.cleaned_data["phone"]
profile.user_type = form2.cleaned_data["user_type"]
profile.save()
if profile.user_type in [Profile.PMEmployee, Profile.PMContractConsultant, Profile.PMBoth]:
prospect = Prospect(profile=profile)
prospect.save()
else:
employer = Employer(profile=profile)
employer.save()
return JsonResponse({'message':'success'})
else:
return JsonResponse({'message':form2.errors})
else:
form1 = RegisterForm()
form2 = ProfileForm()
return render(request, 'profile/register.html', {'form1':form1, 'form2':form2, 'userType':user_type, 'userTypeChoices':userTypeChoices})
forms.py
class RegisterForm(UserCreationForm):
email = forms.EmailField(label = "Email")
first_name = forms.CharField(label = "First name")
last_name = forms.CharField(label = "Last name")
class Meta:
model = User
fields = ("email", "first_name", "last_name")
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise forms.ValidationError('Email already in use.')
return email
def save(self, commit=True):
user = super(RegisterForm, self).save(commit=False)
user.username = self.cleaned_data["email"]
if commit:
user.save()
return user
models.py
from __future__ import unicode_literals
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
phone = models.CharField(max_length=200, null=True, blank=True)
RecruiterAgency = 'ra'
RecruiterInternal = 'ri'
HiringManager = 'hm'
PMEmployee = 'pe'
PMContractConsultant = 'pc'
PMBoth = 'pm'
USER_TYPE_CHOICES = (
(RecruiterAgency, 'Recruiter - Agency'),
(RecruiterInternal, 'Recruiter - Internal'),
(HiringManager, 'Hiring Manager'),
(PMEmployee, 'PM - Employee'),
(PMContractConsultant, 'PM - Contract / Consultant'),
(PMBoth, 'PM - Both'),
)
user_type = models.CharField(max_length=2, choices=USER_TYPE_CHOICES, default=RecruiterAgency)
company = models.CharField(max_length=200, null=True, blank=True)
bio = models.TextField(null=True, blank=True)
accepted_tos = models.BooleanField(default=False)
profile_photo = models.ImageField(null=True, blank=True, upload_to="avatar")
def __str__(self):
return "%s's profile" % self.user.email
class Prospect(models.Model):
profile = models.OneToOneField(Profile, on_delete=models.CASCADE, null=True, blank=True, related_name="profile_prospects")
RestrictedCompany = models.ForeignKey(RestrictedCompany, on_delete=models.CASCADE, null=True, blank=True, related_name="company_prospects")
badge = models.ManyToManyField(Badge, blank=True, related_name="badge_prospects")
certification = models.ManyToManyField(Certification, blank=True, related_name="certification_prospects")
LargestProject1 = 'lp1'
LargestProject2 = 'lp2'
LargestProject3 = 'lp3'
LargestProject4 = 'lp4'
LargestProject5 = 'lp5'
LARGEST_PROJECT_CHOICES = (
(LargestProject1, '$1-100,000'),
(LargestProject2, '$100,001-$500,000'),
(LargestProject3, '$500,001-$1,000,000'),
(LargestProject4, '$1,000,001-$5,000,000'),
(LargestProject5, '$5,000,001+'),
)
largest_project = models.CharField(max_length=3, choices=LARGEST_PROJECT_CHOICES, default=LargestProject1)
add_to_pool = models.BooleanField(default=False)
LargestTeam1 = 'lt1'
LargestTeam2 = 'lt2'
LargestTeam3 = 'lt3'
LargestTeam4 = 'lt4'
LargestTeam5 = 'lt5'
LargestTeam6 = 'lt6'
LARGEST_TEAM_CHOICES = (
(LargestTeam1, '0-5'),
(LargestTeam2, '6-10'),
(LargestTeam3, '11-20'),
(LargestTeam4, '21-50'),
(LargestTeam5, '51-100'),
(LargestTeam6, '100+'),
)
largest_team = models.CharField(max_length=3, choices=LARGEST_TEAM_CHOICES, default=LargestTeam1)
Contract = 'ct'
FullTimeEmployee = 'fe'
Both = 'bo'
Position_Type_CHOICES = (
(Contract, 'Contract'),
(FullTimeEmployee, 'Full-Time Employee'),
(Both, 'Both'),
)
position_type = models.CharField(max_length=2, choices=Position_Type_CHOICES, default=Contract)
resume = models.FileField(null=True, blank=True, upload_to="resume")
EntryLevel = 'el'
Junior = 'jn'
Professional = 'pr'
TIER_CHOICES = (
(EntryLevel, 'Entry-Level'),
(Junior, 'Junior'),
(Professional, 'Professional'),
)
tier = models.CharField(max_length=2, choices=TIER_CHOICES, default=EntryLevel)
billable_rate = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
annual_salary = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
total_years_exp = models.DecimalField(max_digits=4, decimal_places=2, null=True, blank=True)
def __str__(self):
return "%s" % self.profile.user.email
class Employer(models.Model):
profile = models.OneToOneField(Profile, on_delete=models.CASCADE, null=True, blank=True, related_name="profile_employers")
title = models.CharField(max_length=200, null=True, blank=True)
street = models.CharField(max_length=200, null=True, blank=True)
city = models.CharField(max_length=200, null=True, blank=True)
zip = models.CharField(max_length=200, null=True, blank=True)
state = models.CharField(max_length=200, null=True, blank=True)
country = models.CharField(max_length=200, null=True, blank=True)
def __str__(self):
return "%s" % self.profile.user.email
The comments were correct and very helpful! I needed to rename the "email" field on the RegisterForm to "username":
class RegisterForm(UserCreationForm):
username = forms.EmailField(label = "Email")
first_name = forms.CharField(label = "First name")
last_name = forms.CharField(label = "Last name")
class Meta:
model = User
fields = ("username", "first_name", "last_name")
def clean_email(self):
username = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise forms.ValidationError('Email already in use.')
return username
def save(self, commit=True):
user = super(RegisterForm, self).save(commit=False)
user.username = self.cleaned_data["email"]
if commit:
user.save()
return user
You have come across this issue where the UserCreationForm crashes if USERNAME_FIELD (in your case username) is not a form field.
The issue only affects Django 1.10, it doesn't affect earlier versions of Django. It will be fixed in the upcoming Django 1.10.1 release.
In models.py:
class Client(AbstractBaseUser):
username = models.CharField(max_length=32, unique=True)
email = models.EmailField('email address', unique=True, db_index=True)
avatar = models.ImageField('avatar', upload_to='avatars')
id = id(object)
class Order(models.Model):
class Meta():
db_table = 'order'
short_desc = models.CharField(max_length=30)
subject = models.ForeignKey(Subject, blank=True)
user_id = models.ForeignKey('Client', to_field='id', related_name='client_id', default='0', blank=True)
performer_id = models.ForeignKey('Client', to_field='id', related_name='performer_id', default='0', blank=True)
worktype = models.ForeignKey(Type, blank=True)
level = models.IntegerField(default=0, blank=True)
readiness = models.BooleanField(default=False, blank=True)
description = models.TextField(max_length=2000, blank=True)
file = models.FileField(upload_to='orderfiles', blank=True)
#maxdate = models.DateField(blank=True)
addate = models.DateField(auto_now=True, blank=True)
price = models.IntegerField(max_length=10, blank=True)
responses = models.IntegerField(blank=True)
In forms.py:
class AddOrderForm(forms.ModelForm):
short_desc = forms.CharField(widget=forms.TextInput,label="Краткое описание(послужит именем)")
subject = forms.ModelChoiceField(queryset=Subject.objects.all(), label="Предмет")
worktype = forms.ModelChoiceField(queryset=Type.objects.all(), label="Тип")
level = forms.IntegerField(widget=forms.TextInput,label="Уровень сложности (от 1 до 5)")
description = forms.CharField(widget=forms.TextInput,label="Полное описание")
#maxdate = forms.DateField(widget=forms.TextInput,label="maxdate")
price = forms.IntegerField(widget=forms.TextInput,label="Ваша цена")
responses = forms.IntegerField(widget=forms.TextInput,label="Кол-во ответов на заказ")
class Meta:
model = Order
fields = ['short_desc', 'level', 'description', 'price', 'responses', 'subject', 'worktype']
In views.py:
def addorder(request, user_id):
"""
Adding Order view
"""
if request.POST:
form = AddOrderForm(request.POST)
if form.is_valid():
form.save()
return redirect('/')
else:
return redirect('/')
auth1 = auth.get_user(request).username
return render_to_response('customer.html', { 'form': form,'username' : auth1}, context_instance=RequestContext(request))
I need the field user_id in class Order to be initialized immediately after adding order(). Where should I do it and in which way? I need something like this logic: Client adds an Order through AddOrderForm and then user_id field of just added object of class Order has to be initialized with an object of class Client, whose id equals user_id in parameters of addorder() function.
You can do that using commit=False while saving the form. This is typical way of saving the object using model form which has fewer fields.
def addorder(request, user_id):
"""
Adding Order view
"""
if request.POST:
form = AddOrderForm(request.POST)
if form.is_valid():
order = form.save(commit=false)
order.client_id = Client.objects.get(id=user_id)
order.save()
return redirect('/')
else:
return redirect('/')
auth1 = auth.get_user(request).username
return render_to_response('customer.html',
{ 'form': form,'username' : auth1},
context_instance=RequestContext(request))
Disclaimer: Handle errors e.g. Client.objects.get() may fail. Use appropriate fields to search.
I'm starting with Django. I have 3 models, a parent class "Cliente" and two child classes, "Persona" and "Empresa".
models.py
class Cliente(models.Model):
idcliente = models.AutoField(unique=True, primary_key=True)
direccion = models.CharField(max_length=45L, blank=True)
telefono = models.CharField(max_length=45L, blank=True)
email = models.CharField(max_length=45L, blank=True)
def __unicode__(self):
return u'Id: %s' % (self.idcliente)
class Meta:
db_table = 'cliente'
class Empresa(models.Model):
idcliente = models.ForeignKey('Cliente', db_column='idcliente', primary_key=True)
cuit = models.CharField(max_length=45L)
nombre = models.CharField(max_length=60L)
numero_ingresos_brutos = models.CharField(max_length=45L, blank=True)
razon_social = models.CharField(max_length=45L, blank=True)
def __unicode__(self):
return u'CUIT: %s - Nombre: %s' % (self.cuit, self.nombre)
class Meta:
db_table = 'empresa'
class Persona(models.Model):
idcliente = models.ForeignKey('Cliente', db_column='idcliente', primary_key=True)
representante_de = models.ForeignKey('Empresa', null=True, db_column='representante_de', blank=True, related_name='representa_a')
nombre = models.CharField(max_length=45L)
apellido = models.CharField(max_length=45L)
def __unicode__(self):
return u'Id: %s - Nombre completo: %s %s' % (self.idcliente, self.nombre, self.apellido)
class Meta:
db_table = 'persona'
I want to manage a class and its parent in the same view. I want to add, edit and delete "Cliente" and "Persona"/"Cliente" in the same form. Can you help me?
There is a good example in the Documentation Here.
I wrote this based on the documentation, so it is untested.
def manage_books(request, client_id):
client = Cliente.objects.get(pk=client_id)
EmpresaInlineFormSet = inlineformset_factory(Cliente, Empresa)
if request.method == "POST":
formset = EmpresaInlineFormSet(request.POST, request.FILES, instance=author)
if formset.is_valid():
formset.save()
# Do something. Should generally end with a redirect. For example:
return HttpResponseRedirect(client.get_absolute_url())
else:
formset = EmpresaInlineFormSet(instance=client)
return render_to_response("manage_empresa.html", {
"formset": formset,
})
I want to set the form value..i am not displaying it in form but want to set the value of field in my view?
This my modelform:
class payment_detail(models.Model):
status = (
('Paid','Paid'),
('Pending','Pending'),
)
id = models.AutoField(primary_key=True)
#ref_id = models.CharField(max_length=32, default=_createId)
#user = models.ForeignKey(User, editable = False)
payment_type= models.ForeignKey(Payment_types,to_field = 'payment_types', null=True, blank=True)
job_post_id= models.ForeignKey(jobpost,to_field = 'job_id', null=True, blank=True)
price= models.ForeignKey(package,to_field = 'amount', null=True, blank=True)
created_date = models.DateField(("date"), default=datetime.date.today)
payment_status = models.CharField(max_length=255, choices=status,default='Pending')
transaction_id = models.CharField(max_length=255, null=True, blank=True)
payment_date = models.DateField(null=True, blank=True)
email = models.CharField(max_length=255, null=True)
def __unicode__(self):
#return self.user
return unicode(self.id)
#return self.ref_id
return unicode(self.payment_type)
return unicode(self.job_post_id)
return unicode(self.price)
return unicode(self.created_date)
return unicode(self.payment_status)
return unicode(self.payment_date)
return unicode(self.transaction_id)
return unicode(self.email)
admin.site.register(payment_detail)
my View:
def payment(request):
if "pk" in request.session:
pk = request.session["pk"]
Country = request.session["country"]
price = package.objects.filter(item_type__exact='Job' ,country__country_name__exact=Country, number_of_items__exact='1')
if request.method == 'POST':
entity = payment_detail()
form = jobpostForm_detail(request.POST, instance=entity)
if form.is_valid():
#form.fields["transaction_id"] = 100
form.save()
#message = EmailMessage('portal/pay_email.html', 'Madeeha ', to=[form.cleaned_data['email']])
#message.send()
return HttpResponseRedirect('/portal/pay/mail/')
else:
form = jobpostForm_detail(initial={'transaction_id': "US"})
c = {}
c.update(csrf(request))
return render_to_response('portal/display.html',{
'form':form,'price':price
},context_instance=RequestContext(request))
like i want to set the value of job_location and don't want to display it in form..
forms.py
//this is how you hide the field
class jobpostForm(ModelForm):
def __init__(self, *args, **kwargs):
super(jobpostForm, self).__init__(*args, **kwargs)
self.fields['job_location'].widget = forms.HiddenInput()
class Meta:
model = jobpost
views.py
.........
if request.method == 'POST':
entity = payment_detail(transaction_id="US") #change
form = jobpostForm_detail(request.POST, instance=entity)
if form.is_valid():
#form.fields["transaction_id"] = 100
form.save()
#message = EmailMessage('portal/pay_email.html', 'Madeeha ', to=[form.cleaned_data['email']])
#message.send()
return HttpResponseRedirect('/portal/pay/mail/')
else:
form = jobpostForm_detail()
..................