ModelForm save() does not work after changing model - django

I'm playing around with a django-survey from jessykate (https://github.com/jessykate/django-survey) and altered the models.py. Now the save() method does not work anymore and I do not get why that is.
models.py (see comment #)
class Response(models.Model):
'''
a response object is just a collection of questions and answers with a
unique interview uuid
'''
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
survey = models.ForeignKey(Survey)
# interviewer = models.CharField('Name of Interviewer', max_length=400)
# interviewee = models.CharField('Name of Interviewee', max_length=400)
# conditions = models.TextField('Conditions during interview', blank=True, null=True)
# comments = models.TextField('Any additional Comments', blank=True, null=True)
interview_uuid = models.CharField("Interview unique identifier", max_length=36)
def __unicode__(self):
return ("response %s" % self.interview_uuid)
views.py (original)
def SurveyDetail(request, id):
survey = Survey.objects.get(id=id)
category_items = Category.objects.filter(survey=survey)
categories = [c.name for c in category_items]
print 'categories for this survey:'
print categories
if request.method == 'POST':
form = ResponseForm(request.POST, survey=survey)
if form.is_valid():
response = form.save()
return HttpResponseRedirect("/confirm/%s" % response.interview_uuid)
else:
form = ResponseForm(survey=survey)
print form
# TODO sort by category
return render(request, 'survey.html', {'response_form': form, 'survey': survey, 'categories': categories})
forms.py (see comment #)
class ResponseForm(models.ModelForm):
class Meta:
model = Response
# fields = ('interviewer', 'interviewee', 'conditions', 'comments')
[...]
def save(self, commit=True):
''' save the response object '''
response = super(ResponseForm, self).save(commit=False)
response.survey = self.survey
response.interview_uuid = self.uuid
response.save()
'''
create an answer object for each question and associate it with this
response.
'''
for field_name, field_value in self.cleaned_data.iteritems():
if field_name.startswith("question_"):
# warning: this way of extracting the id is very fragile and
# entirely dependent on the way the question_id is encoded in the
# field name in the __init__ method of this form class.
q_id = int(field_name.split("_")[1])
q = Question.objects.get(pk=q_id)
if q.question_type == Question.TEXT:
a = AnswerText(question = q)
a.body = field_value
elif q.question_type == Question.RADIO:
a = AnswerRadio(question = q)
a.body = field_value
elif q.question_type == Question.SELECT:
a = AnswerSelect(question = q)
a.body = field_value
elif q.question_type == Question.SELECT_MULTIPLE:
a = AnswerSelectMultiple(question = q)
a.body = field_value
elif q.question_type == Question.INTEGER:
a = AnswerInteger(question = q)
a.body = field_value
print "creating answer to question %d of type %s" % (q_id, a.question.question_type)
print a.question.text
print 'answer value:'
print field_value
a.response = response
a.save()
return response
So what happens is when I save a survey I'll get the exact same page with all my input instead of a confirm page.
Any clues?

You're currently modifying third party code within your project. The difficulty you're facing is a quick lesson (that we all learn) in why this is a Bad Idea™. From looking at your code you seem to just want to strip off some fields from a Response model. A better solution to that problem is to write your own MyResponse model and use that, rather than editing the third party app's source.
If you insist on using your modifications (don't insist on using your modifications) then you need to identify why the form.is_valid() is False (This is a guess, "save() isn't working" is very vague but you haven't posted an error traceback so I'm assuming there isn't one). Your form has errors in it and if you access them:
for field, errors in form.errors.items():
print field
for error in errors:
print error
Then they will give you a better idea what is happening.
Edit: From the errors you posted you can see where the problem is, you're calling form.is_valid() when the form is missing the survey attribute, so is_valid() evaluates to False and your form's save() method never even gets called. set form.survey = survey before you call form.is_valid() and see what happens.

Related

Testing a form. What'happening? is this really working?

I have a django form with a field directed to a foreignkey. Initially is empty, I mean without any option. Javascript will add options based on another form field.
It's working but I want to test it, without using Selenium (and so, without using javascript).
After numerous attempts I write this code and apparently it's working but I don't know why and I'm not sure it's really working.
def test_form_validation(self):
maschio = Gender.objects.create(name_en='Male', name_it='Maschio')
nome = NameType.objects.create(name_en='Name', name_it='Nome')
romani = NameLanguage.objects.create(
name_en='Romans', name_it='Romani')
romani.syntax.add(nome)
form = NameForm({'nametype': nome.id, 'gender': maschio.id,
'name': 'Remo', 'namelanguage': romani.id})
# print('1', form)
form.fields['nametype'].initial = nome.id
print('2', form)
form.save()
self.assertEqual(Name.objects.all().count(), 1)
my_name = Name.objects.first()
self.assertEqual(my_name.name, 'remo')
self.assertEqual(my_name.nametype, nome)
self.assertEqual(my_name.gender, maschio)
self.assertEqual(my_name.namelanguage, romani)
print('end of test')
# print('1', form) is commented out because with that the test will be give error on line form.save() (ValueError: The Name could not be created because the data didn't validate.). Without that line the test pass (!?!).
I will expected an error when I call my form bounded because nametype has no option to choose from. nome.id is not one of the ammissible choices. And in effect, as I said, it will give an error if I ask to print the form.
<tr><th><label for="id_nametype">Tipo:</label></th><td><select name="nametype" style="width:170px" disabled id="id_nametype">
<option value="0">0</option>
</select></td></tr>
So, is this ok? and why it's working? what's happening?
Thank you
Edit
My form.py:
class NameForm(forms.ModelForm):
"""
form to add names to the database
input: nothing
output: the form
"""
class Meta:
model = Name
fields = ['namelanguage', 'nametype', 'gender', 'name']
widgets = {
'gender': forms.RadioSelect(),
}
def clean_name(self):
data = self.cleaned_data['name'].lower()
if data[0] not in "abcdefghijklmnopqrstuvwxyz":
data = "#" + data
return data
def __init__(self, *args, **kwargs):
try:
last_language = kwargs.get('initial')['namelanguage']
last_nametype = kwargs.get('initial')['nametype']
choices = get_nametype_choices(last_language)
gender_variable = get_gender_variable(last_nametype)
except (TypeError, KeyError):
print('except nameform')
choices = [(0, 0)]
gender_variable = True
super(NameForm, self).__init__(*args, **kwargs)
self.fields['name'].widget.attrs.update({'autofocus': ''})
self.fields['nametype'].widget.attrs.update({'style': 'width:170px'})
self.fields['nametype'].choices = choices
if choices[0][0] == 0:
self.fields['nametype'].disabled = True
if not gender_variable:
print('gender non variable')
self.fields['gender'].disabled = True
self.fields['gender'].required = False
try:
self.fields['gender'].initial = Gender.objects.get(
name_it='neutro').id
except ObjectDoesNotExist:
self.fields['gender'].initial = None
else:
self.fields['gender'].disabled = False
self.fields['gender'].required = True
def get_nametype_choices(last_language):
"""
function called by NameForm__init__ to create the list of choices
for nametype according to the language
input: language
output: a list of tuple with the appropriate nametype for the language
"""
# print('get_nametype_choices')
lista_file = []
try:
language = NameLanguage.objects.get(id=last_language)
choices = language.syntax.all()
for choice in choices:
lista_file.append((choice.id, choice))
except ObjectDoesNotExist:
print('except get_nametype_choices')
lista_file.append((0, 'Scegli il linguaggio'))
return lista_file
get_nametype_choices is not called by the test because last_language = kwargs.get('initial')['namelanguage'] in NameForm.__init__ gives an exception.

Django Multiselect, how to override choices properly

this is my forms.py
CHOICES = []
class salDeptChartForm(forms.Form):
company = forms.CharField(max_length=2,label = 'Firma',help_text='A valid email address, please.')
date_validfrom = forms.DateField(label = 'Bu Tarihten',required=False)
date_validuntil = forms.DateField(label = 'Bu Tarihe Kadar',required=False)
saldept = forms.MultipleChoiceField(label = 'Satış Departmanları',choices=CHOICES, widget=forms.CheckboxSelectMultiple())
this is where I override the choices in my view.
form = salDeptChartForm(initial={'company':'01'})
saldeptlist = saleinstance.fetchSalDept()
form.fields['saldept'].choices = saldeptlist <this is where I override>
problem occurs when I select one of the options. form doesnt get validate.
Select a valid choice. * is not one of the available choices.
I think, even I override the choices in my view django still checks with previous choices itially I created. I get the correct html output tough.
How to overcome this?
thx
complete view code is there.
form initiates twice one for get and one for post, I dont know if its best either.
def salDept(request):
member_id = request.session['member_id']
saleinstance = sale(member_id)
chartinstance = charts(member_id)
if request.method == 'GET':
form = salDeptChartForm(initial={'company':'01'}) <first init>
saldeptlist = saleinstance.fetchSalDept() <its a list>
form.fields['saldept'].choices = saldeptlist <override choices>
print 'get worked'
return render(request, 'chart/sale/salDept.html',locals())
if request.method == 'POST':
form = salDeptChartForm(request.POST) <second init>
print 'post worked'
if form.is_valid(): <fails>
print 'valid'
company = form.cleaned_data['company']
vfr = form.cleaned_data['date_validfrom']
vun = form.cleaned_data['date_validuntil']
validfrom = formatDate(vfr)
validuntil = formatDate(vun)
selectedSalDepts = request.POST.getlist('saldept')
else:
print 'not valid'
print form.errors
resultdict = chartinstance.salesBySaldept(company,selectedSalDepts,validfrom, validuntil)
form = salDeptChartForm(initial={'company':company,'date_validfrom':request.POST['date_validfrom'], 'date_validuntil':request.POST['date_validuntil']})
domcache = 'true'
return render(request, 'chart/sale/salDept.html',locals())
Okay, you need override the init() of the form to do accomplish this.
class SomeForm(forms.Form):
email = forms.EmailField(label=(u'Email Address'))
users = forms.MultipleChoiceField(choices=[(x, x) for x in User.objects.all()]
)
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(SomeForm, self).__init__(*args, **kwargs)
self.fields['users'].choices = [(x, x) for x in User.objects.filter(name__contains='Patel')]
def clean(self):
return self.cleaned_datas
Here in line number (3) you can see that I have provided all the possible choices and then in the init I have filtered the choices, this is important because Django validates your submitted request from the former and displays the choices from the latter
Your validation fails because you only overwrite the choices on the GET method. You don't do anything for the POST, so as far as Django knows, no choice is valid for the POST. Adding the choices to POST should fix your problem.

Duplication check before saving the form data in Django

I got a form as following:
class CourseAddForm(forms.ModelForm):
"""Add a new course"""
name = forms.CharField(label=_("Course Name"), max_length=100)
description = forms.Textarea()
course_no = forms.CharField(label=_("course Number"), max_length=15)
#Attach a form helper to this class
helper = FormHelper()
helper.form_id = "addcourse"
helper.form_class = "course"
#Add in a submit and reset button
submit = Submit("Add", "Add New Record")
helper.add_input(submit)
reset = Reset("Reset", "Reset")
helper.add_input(reset)
def clean(self):
"""
Override the default clean method to check whether this course has been already inputted.
"""
cleaned_data = self.cleaned_data
name = cleaned_data.get('name')
hic = cleaned_data.get('course_no')
try:
course=Course.objects.get(name=name)
except Course.DoesNotExist:
course=None
if course:
msg = u"Course name: %s has already exist." % name
self._errors['name'] = self.error_class([msg])
del cleaned_data['name']
return cleaned_data
else:
return self.cleaned_data
class Meta:
model = Course
As you can see I overwrote the clean method to check whether this course has already existed in the database when the user is trying to add it. This works fine for me.
However, when I want to add the same check for the form for editing, the problem happened. Because it is editing, so the record with same course name has already exist in the DB. Thus, the same check would throw error the course name has already exist. But I need to check the duplication in order to avoid the user updating the course name to another already existed course name.
I am thinking of checking the value of the course name to see if it is changed. If it has been changed, than I can do the same check as above. If it has not been changed, I don't need to do the check. But I don't know how can I obtain the origin data for editing.
Does anyone know how to do this in Django?
My view looks as following:
#login_required
#csrf_protect
#never_cache
#custom_permission_required('records.change_course', 'course')
def edit_course(request,course_id):
# See if the family exists:
try:
course = Course.objects.get(id=course_id)
except Course.DoesNotExist:
course = None
if course:
if request.method == 'GET':
form = CourseEditForm(instance=course)
return render_to_response('records/add.html',
{'form': form},
context_instance=RequestContext(request)
)
elif request.method == 'POST':
form = CourseEditForm(request.POST, instance=course)
if form.is_valid():
form.save()
return HttpResponseRedirect('/records/')
# form is not valid:
else:
error_message = "Please correct all values marked in red."
return render_to_response('records/edit.html',
{'form': form, 'error_message': error_message},
context_instance=RequestContext(request)
)
else:
error = "Course %s does not exist. Press the 'BACK' button on your browser." % (course)
return HttpResponseRedirect(reverse('DigitalRecords.views.error', args=(error,)))
Thank you.
I think you should just set unique=True on the Course.name field and let the framework handle that validation for you.
Update:
Since unique=True is not the right answer for your case, you can check this way:
def clean(self):
"""
Override the default clean method to check whether this course has
been already inputted.
"""
cleaned_data = self.cleaned_data
name = cleaned_data.get('name')
matching_courses = Course.objects.filter(name=name)
if self.instance:
matching_courses = matching_courses.exclude(pk=self.instance.pk)
if matching_courses.exists():
msg = u"Course name: %s has already exist." % name
raise ValidationError(msg)
else:
return self.cleaned_data
class Meta:
model = Course
As a side note, I've also changed your custom error handling to use a more standard ValidationError.
I believe excluding the current instance id from the results would solve the problem:
from django.db.models import Q
try:
qs = Course.objects.filter(name=self.cleaned_data.get('name'))
if self.instance.pk is not None:
qs = qs.filter(~Q(pk=self.instance.pk))
course = qs.get()
except Course.DoesNotExist:
course = None
However as dokkaebi pointed out, unique is really the better way to go with this, as this solution is vulnerable to race conditions. I'm not sure what your datamodel looks like but I suspect defining
class Meta:
unique_together = ('department', 'name')
should accomplish what you want.

how to overide in forms queryset none() attribute and somehow allow to save the field?

I have models.py
class Visit(Model):
reference_visit = models.ForeignKey('self',
help_text="Visit needs a refrence to Prior Visits",
null=True, blank=True)
show_prior_responses = models.BooleanField(default=False,
help_text="Show PriorResponses")
# has many field but i am making it short.
def __unicode__(self):
result = """Visit id:%s pt:%s""" % (self.id, self.patient.id)
return result
forms.py
class VisitSetupForm(Form):
list_visit_ids = ModelChoiceField(
queryset=Visit.objects.none(),
empty_label='Select Revisit ID',required=False)
show_prior_visit = ModelChoiceField(
queryset=User.objects.all(),
empty_label="Select User for Revisit",required = False)
has many but question is on list_visit_ids.
views.py
def setup(request):
"""
Allow an Admin user the ability to setup a patient & visit all at once.
"""
if request.user.is_superuser:
form_class = AdminVisitSetupForm
all_topics = True
else:
form_class = VisitSetupForm
all_topics = False
f = form_class()
# Get a list of topics for each report.
report_topics = {}
for r in Interview.objects.all():
report_topics[r.id] = [t['ad'] for t in r.topics.values('ad')]
data = {
'superuser':request.user.is_superuser,
'report_topics':simplejson.dumps(report_topics)
}
try:
request.user.reviewer
data['reviewer'] = True
except:
pass
if request.method == "POST":
f = form_class(request.POST)
if f.is_valid():
# Create the patient, generate a password, and send them on their way.
cd = f.cleaned_data
patient = None
if cd['revisit']:
# Check for an existing user first.
try:
patient = Patient.objects.get(username=cd['username'])
except Patient.DoesNotExist, e:
data['form'] = f
data['msg'] = 'There is no user with this username.'
return render_to_response('visit/setup.html', data, context_instance=RequestContext(request))
admin_user = get_user(request)
organization = None
if admin_user:
organization = admin_user.organization
if patient and not request.user.is_superuser:
# Make sure the patient they've selected is one of their own.
if patient.organization != organization:
return HttpResponseForbidden('You are not allowed to see this page.')
if not patient:
password = generate_password()
user = User.objects.create_user(cd['username'], cd['contact_email'], password)
user.first_name = cd['first_name']
user.last_name = cd['last_name']
user.save()
patient = Patient(
user=user,
username=user.username,
contact_phone=cd['contact_phone'],
date_of_birth=cd['date_of_birth'],
email=user.email,
first_name=user.first_name,
gender=cd['gender'],
last_name=user.last_name,
maiden_name=cd['maiden_name'],
organization=organization,
patient_type=cd['patient_type'],
security_answer=cd['security_answer'],
security_question=cd['security_question'],
)
patient.save()
# Send them an email.
t = loader.get_template('www/new_account.txt')
c = Context({
'password':'%s-%s-%s' % (password[:3], password[3:5], password[5:]),
'patient':patient
})
msg = t.render(c)
try:
send_mail(
'A request by your physician to do an online medical history before your appointment.',
msg,
'support#careprep.com',
[user.email]
)
except Exception, e:
log.error('Could not send email for new account %s because: [%s]' % (user.username, e))
request.session['password'] = password
# Create the Visit, too.
interview = cd['interview']
list_visit_ids = cd['list_visit_ids']
print list_visit_ids
visit = Visit(
reference_visit = cd['list_visit_ids'],
show_prior_responses = cd['show_prior_responses'],
patient=patient
)
if request.user.is_superuser:
topics = cd['topics']
else:
topics = set(list(interview.topics.all()) + list(cd['topics']))
reviewer_mode = cd.get('reviewer_mode') or patient.patient_type == 'Reviewer'
url, visit = initialize_visit(
request,
patient=patient,
starting_section=interview.starting_section,
visit_title='%s %s' % (patient, interview.title),
topics=topics,
reviewer_mode=reviewer_mode,
chief_complaint=cd['chief_complaint'],
location=cd['interview_site'],
reference_visit = cd['list_visit_ids'],
show_prior_responses = cd['show_prior_responses'],
)
next_url = "/visit/confirmation/%s/%s/?next=%s" % (patient.user.id, interview.id, url)
else:
v = Visit.objects.get(pk=request.POST['list_visit_ids'])
print v
return HttpResponseRedirect(next_url)
# all the fields that are not given pls ignore.
The template is fine.
Now watch forms.py when i do list_visit_ids = ModelChoiceField(queryset=Visit.objects.all(), empty_label='Select Revisit ID',required=False) It works perfectly fine on my local machine.But on my server it has around 6000 visit objects so this page hangs or i should say keep on loading.
So initially i changed it to list_visit_ids = ModelChoiceField(queryset=Visit.objects.none(), empty_label='Select Revisit ID',required=False)
Now i know that by this the form becomes invalid and should go to the else part Now my question how do i make reference_visit=cd['list_visit_ids'] in else (form is invalid)
case save().How do i override the none() attribute.
Thanks in advance i will really appreciate.
If your goal is to save your html page load by removing the 6000 choices (which I've done too: 10000+ <option> fields wrapped by misc html will absolutely choke a page), you shouldn't be using a ChoiceField at all. By setting queryset=Visit.objects.none() you're allowing zero choices and nothing passed in will validate.
You either show 6000 select item drop downs, radio boxes, etc., or find a way to /not/ have a giant select drop down (such as a hidden input or charfield), not fake around a ModelChoiceField who's main purpose is to populate that select drop down and validate.
In short: don't use a ModelChoiceField if you're not going to be using the html choices generated by it. Use something else and do the validation / model pulling yourself via the clean_FOO methods.
class MyForm(forms.Form):
my_input = forms.CharField()
def clean_my_input(self):
input = self.cleaned_data.get('my_input')
try:
return MyModel.objects.get(pk=input) # add a filter here if you want
# (whatever filters you were using in the queryset argument)
except MyModel.DoesNotExist:
raise forms.ValidationError("Doesn't exist / is invalid")
return input

Why doesn't Django enforce my unique_together constraint as a form.ValidationError instead of throwing an exception?

Edit: While this post is a duplicate of Django's ModelForm unique_together validation, the accepted answer here of removing the 'exclude' from the ModelForm is a much cleaner solution than the accepted answer in the other question.
This is a follow-up to this question.
If I don't explicitly check the unique_together constraint in the clean_title() function, django throws an exception:
IntegrityError at /journal/journal/4
duplicate key value violates unique constraint "journal_journal_owner_id_key"
Request Method: POST
Request URL: http://localhost:8000/journal/journal/4
Exception Type: IntegrityError
Exception Value: duplicate key value violates unique constraint "journal_journal_owner_id_key"
Exception Location: /Library/Python/2.6/site-packages/django/db/backends/util.py in execute, line 19
However I was under the impression that Django would enforce this constraint nicely by raising a ValidationError, not with an exception I need to catch.
Below is my code with an additional clean_title() method I use as a work-around. But I want to know what I'm doing wrong such that django is not enforcing the constraint in the expected manner.
Thanks.
Model code:
class Journal (models.Model):
owner = models.ForeignKey(User, related_name='journals')
title = models.CharField(null=False, max_length=256)
published = models.BooleanField(default=False)
class Meta:
unique_together = ("owner", "title")
def __unicode__(self):
return self.title
Form code:
class JournalForm (ModelForm):
class Meta:
model = models.Journal
exclude = ('owner',)
html_input = forms.CharField(label=u'Journal Content:', widget=TinyMCE(attrs={'cols':'85', 'rows':'40'}, ), )
def clean_title(self):
title = self.cleaned_data['title']
if self.instance.id:
if models.Journal.objects.filter(owner=self.instance.owner, title=title).exclude(id=self.instance.id).count() > 0:
raise forms.ValidationError(u'You already have a Journal with that title. Please change your title so it is unique.')
else:
if models.Journal.objects.filter(owner=self.instance.owner, title=title).count() > 0:
raise forms.ValidationError(u'You already have a Journal with that title. Please change your title so it is unique.')
return title
View Code:
def journal (request, id=''):
if not request.user.is_active:
return _handle_login(request)
owner = request.user
try:
if request.method == 'GET':
if '' == id:
form = forms.JournalForm(instance=owner)
return shortcuts.render_to_response('journal/Journal.html', { 'form':form, })
journal = models.Journal.objects.get(id=id)
if request.user.id != journal.owner.id:
return http.HttpResponseForbidden('<h1>Access denied</h1>')
data = {
'title' : journal.title,
'html_input' : _journal_fields_to_HTML(journal.id),
'published' : journal.published
}
form = forms.JournalForm(data, instance=journal)
return shortcuts.render_to_response('journal/Journal.html', { 'form':form, })
elif request.method == 'POST':
if LOGIN_FORM_KEY in request.POST:
return _handle_login(request)
else:
if '' == id:
journal = models.Journal()
journal.owner = owner
else:
journal = models.Journal.objects.get(id=id)
form = forms.JournalForm(data=request.POST, instance=journal)
if form.is_valid():
journal.owner = owner
journal.title = form.cleaned_data['title']
journal.published = form.cleaned_data['published']
journal.save()
if _HTML_to_journal_fields(journal, form.cleaned_data['html_input']):
html_memo = "Save successful."
else:
html_memo = "Unable to save Journal."
return shortcuts.render_to_response('journal/Journal.html', { 'form':form, 'saved':html_memo})
else:
return shortcuts.render_to_response('journal/Journal.html', { 'form':form })
return http.HttpResponseNotAllowed(['GET', 'POST'])
except models.Journal.DoesNotExist:
return http.HttpResponseNotFound('<h1>Requested journal not found</h1>')
UPDATE WORKING CODE:
Thanks to Daniel Roseman.
Model code stays the same as above.
Form code - remove exclude statement and clean_title function:
class JournalForm (ModelForm):
class Meta:
model = models.Journal
html_input = forms.CharField(label=u'Journal Content:', widget=TinyMCE(attrs={'cols':'85', 'rows':'40'},),)
View Code - add custom uniqueness error message:
def journal (request, id=''):
if not request.user.is_active:
return _handle_login(request)
try:
if '' != id:
journal = models.Journal.objects.get(id=id)
if request.user.id != journal.owner.id:
return http.HttpResponseForbidden('<h1>Access denied</h1>')
if request.method == 'GET':
if '' == id:
form = forms.JournalForm()
else:
form = forms.JournalForm(initial={'html_input':_journal_fields_to_HTML(journal.id)},instance=journal)
return shortcuts.render_to_response('journal/Journal.html', { 'form':form, })
elif request.method == 'POST':
if LOGIN_FORM_KEY in request.POST:
return _handle_login(request)
data = request.POST.copy()
data['owner'] = request.user.id
if '' == id:
form = forms.JournalForm(data)
else:
form = forms.JournalForm(data, instance=journal)
if form.is_valid():
journal = form.save()
if _HTML_to_journal_fields(journal, form.cleaned_data['html_input']):
html_memo = "Save successful."
else:
html_memo = "Unable to save Journal."
return shortcuts.render_to_response('journal/Journal.html', { 'form':form, 'saved':html_memo})
else:
if form.unique_error_message:
err_message = u'You already have a Lab Journal with that title. Please change your title so it is unique.'
else:
err_message = form.errors
return shortcuts.render_to_response('journal/Journal.html', { 'form':form, 'error_message':err_message})
return http.HttpResponseNotAllowed(['GET', 'POST'])
except models.Journal.DoesNotExist:
return http.HttpResponseNotFound('<h1>Requested journal not found</h1>')
The trouble is that you're specifically excluding one of the fields involved in the unique check, and Django won't run the check in this circumstance - see the _get_unique_checks method in line 722 of django.db.models.base.
Instead of excluding the owner field, I would consider just leaving it out of the template and setting the value explicitly on the data you're passing in on instantiation:
data = request.POST.copy()
data['owner'] = request.user.id
form = JournalForm(data, instance=journal)
Note that you're not really using the power of the modelform here. You don't need to explicitly set the data dictionary on the initial GET - and, in fact, you shouldn't pass a data parameter there, as it triggers validation: if you need to pass in values that are different to the instance's, you should use initial instead. But most of the time, just passing instance is enough.
And, on POST, again you don't need to set the values explicitly: you can just do:
journal = form.save()
which will update the instance correctly and return it.
I think the philosophy here is that unique_together is an ORM concept, not a property of a form. If you want to enforce unique_together for a particular form, you can write your own clean method, which is easy, straightforward, and very flexible:
http://docs.djangoproject.com/en/dev/ref/forms/validation/#cleaning-and-validating-fields-that-depend-on-each-other
This will replace the clean_title method you have written.