I'm trying to update an object, but I'm getting: "primary key must be unique"... The model for Entry:
class Entry(models.Model):
title = models.CharField(max_length=250)
author = models.ForeignKey(User, editable=False)
status = models.IntegerField(choices=data_types.STATUS_CHOICES,
default = data_types.STATUS_DRAFT)
And the view:
#login_required
def edit_entry(request, entry_id='0'):
message = ""
entry = get_object_or_404(Entry.objects, pk=entry_id)
if request.method == 'GET':
form = EntryForm(instance=entry)
else :
if request.method == 'POST':
if request.POST['submit'] == 'Edit':
print "entry id: %s" % entry.id
form = EntryForm(request.POST, instance=entry)
if form.is_valid():
secondEntry = form.save(False)
print "second entry id: %s" % secondEntry.id
form.save()
message = "entry updated"
else:
message = 'There were errors'
return render_to_response(
'myadmin/edit_entry.html',
{ 'entryForm':form,'message': message},
context_instance=RequestContext(request))
So I print both IDs and they are the same: 1. That should make form.save() update the object, but instead, it tries to insert it... any idea?
Well maybe in the line
form = EntryForm(request.POST, instance=entry)
you dont specify an intance parameter, because this is for update an existing model....
Your first form.save(False) produces an Entry object filled with the data from the form, so you should save that Entry object instead of the form again.
secondEntry = form.save(commit=False)
secondEntry.save()
Why do you have form.save() twice? Perhaps that is the problem.
form.save(force_update=True)
Related
I have two select classes that I am trying to create in an unbound form. The data selections are only relevant to the presentation that is created in the view, so are throwaways and do not need to be saved in a model.
The challenge I have is that I can pass in the field listings ok, but how do I set "default" checked / selected values so that the form becomes 'bound'?
views.py
def cards(request):
sort_name = []
sort_name.append("Alphabetic Order")
sort_name.append("Most Popular")
sort_name.append("Least Popular")
sort_name.append("Highest Win Rate")
sort_name.append("Lowest Win Rate")
sort_id = range(len(sort_name))
sort_list = list(zip(sort_id, sort_name))
<more code to make filt_list and zip it>
if request.method == 'POST':
form = cardStatsForm(request.POST, sortList=sort_list, filtList=filt_list)
if form.is_valid():
do something
else:
do something else
else:
form = cardStatsForm(filter_list, sort_list)
forms.py
class cardStatsForm(forms.Form):
def __init__(self, filterList, sortList, *args, **kwargs):
super(cardStatsForm, self).__init__(*args, **kwargs)
self.fields['filts'].choices = filterList
self.fields['filts'].label = "Select player rankings for inclusion in statistics:"
self.fields['sorts'].choices = sortList
self.fields['sorts'].label = "Choose a sort order:"
filts = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, choices=(), required=True)
sorts = forms.ChoiceField(choices=(), required=True)
The difficulty I am having is the the form fails the "is_valid" test since it is not bound, and I have the "required=true" setting (so that the user must select a checkbox / select a value), but I cannot enforce the logic since it seems the form is never 'bound'.
You can use django forms validation or pass defult value in your views.py. It will return unbound forms if value doesn't match with your default value.
let show you how to do it in your views.py:
error_message = None
default_value = "jhone"
if form.is_valid():
name = request.POST['name']
defult_name = jhone
if defult_name != name:
error_message = 'Name must be jhone'
if not error_message:
form.save() #it will only save forms if default value match
else:
do something else
context = {'error_message':error_message,
'default_value': default_value,
'form':form,
} #pass the context in your html template for showing default value and error message
in your .html
{{error_message}}
<input type=text name='name' {%if form.is_bound %} value="{{default_value}} {%endif%}">
I was able to correct my issue by adding "inital=0" and modifying my form call as outlined below:
forms.py
filts = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, choices=(), initial=0, required=True)
sorts = forms.ChoiceField(choices=(), initial=0, required=True)
views.py
if request.method == 'POST':
form = cardStatsForm(data=request.POST, sortList=sort_list, filterList=filter_list)
else:
form = cardStatsForm(filter_list, sort_list)
I have a form containing af MultipleChoiceField where the choices are created dynamic based on the given user
class UpdateForm(forms.Form):
def __init__(self,names,*args,**kwargs):
super(UpdateForm,self).__init__(*args,**kwargs)
self.fields["list_names"] = forms.MultipleChoiceField(choices = zip(names,names),widget=forms.CheckboxSelectMultiple,label="Pick some names")
add_new = forms.BooleanField(initial=True, label="Add new names?",required=False)
delete_missing = forms.BooleanField(label = "Delete names?",required=False)
and it works fine as GET-request, the issues arrives with the post-request:
My view is the following:
def update(request):
user = request.user
list_names = MyModel.objects.filter(user=user).all().values_list("nick_name",flat=True).distinct()
form = UpdateWishlistForm(names =list_names)
if request.method == "POST":
post_form = UpdateForm(request.POST)
if post_form.is_valid():
list_names = post_form.cleaned_data["list_names"]
add_new = post_form.cleaned_data["add_new"]
delete_missing = post_form.cleaned_data["delete_missing"]
messages.success(request, "Success")
context = {
"form":form,
}
redirect("home")
else:
#invalid post_form
messages.error(request, "Error")
context = {
"form":form,
}
return render(request, "discounttracker/update.html")
else: #Get request
context = {
"form":form,
}
return render(request, "myapp/update.html",context=context)
The post_form = UpdateForm(request.POST) does not validate and the post_form.errors is empty.
It does contain data though (before calling post_form.is_valid())
print(post_form)
# UpdateForm: <UpdateForm bound=False, valid=Unknown, fields=(add_new;delete_missing;list_names)>
print(request.POST.dict())
#<QueryDict: {'csrfmiddlewaretoken': ['...'], 'add_new': ['on'], 'list_names': ['test_name_1']}>
but I notice it is not bound, thus not validating. But I cannot understand why it's not "binding" when parsing request.POST?
In the POST request, you need to pass the names as well, so:
list_names = MyModel.objects.filter(user=user).values_list("nick_name",flat=True).distinct()
form = UpdateWishlistForm(names=list_names)
if request.method == 'POST':
post_form = UpdateForm(names=list_names, data=request.POST)
# …
# …
But I would advise to work with a ModelMultipleChoiceField [Django-doc] and thus pass a queryset. Since the nick names apparently can contain duplicates, it might be better to make a Nickname model, and use ForeignKeys to that model.
Following is a snapshot of one of my models in django application:
class Request(models.Model):
device_type = models.ForeignKey(DeviceType,to_field='device_type')
requested_by = models.ForeignKey(User,to_field='username')
urgency = models.ForeignKey(RequestUrgency,to_field='request_urgency')
request_must_be_satisfied_by = models.DateField(null=True)
reason = models.TextField()
status = models.ForeignKey(ResuestStatus,to_field='request_status',default="PENDING")
request_date = models.DateField(default=datetime.now)
def __unicode__(self):
return self.device_type
def __str__(self):
return self.device_type
Code inside the views.py file:
def request_form(request):
if request.method == "POST":
print("Inside out if")
form = RequestForm(request.POST)
print("request form: ", form)
print("request fields ", form.fields)
print("errors: ",form.errors.as_data())
if form.is_valid():
print("errors: ",form.errors.as_data())
if form.is_valid():
cleaned_data = form.cleaned_data
post = form.save(commit = False)
post.requested_by_id = request.user
post.save()
userdata = User.objects.get(username = request.user.username)
useremail = userdata.email
email_body_text = "Hi "+request.user.username+","+"\n"+"Your requested a repair for the following:"+"\n"\
+"Device Type: "+cleaned_data["device_type"]+"\n"\
+"Urgency: "+cleaned_data["urgency"]+"\n"\
+"Request must be satisfied by: "+cleaned_data["request_must_be_satisfied_by"]+"\n"\
+"Reason for new device: "+cleaned_data["reason"]+"\n"\
+"We will try to complete your request at the earliest. In case of any concerns contact the controller of operations."
send_mail(email_subject,email_body_text,config.sender_email_id,[useremail],fail_silently=False)
return redirect("NewRequest")
else:
print("Inside else")
form = RequestForm(initial={'requested_by':request.user})
Tp = DeviceType.objects.all()
Tp = Tp.annotate(text=F('device_type')).values_list('text', flat=True)
print("Tp request form: ", Tp)
Ur = RequestUrgency.objects.all()
Ur = Ur.annotate(text=F('request_urgency')).values_list('text', flat=True)
form.fields["device_type"] = forms.ModelChoiceField(queryset=Tp, empty_label=None)
form.fields["urgency"] = forms.ModelChoiceField(queryset=Ur, empty_label=None)
return render(request, "Form_template.html", {"form": form, \
"name": "Device Request Form"})
Now, I have a function in my views.py that validates the data that is submitted through the django rendered form. Now, I have a situation, I want to print the data sent through the form by concatinating it with a particular string. Whenever I got to do so. I get the following error:
TypeError: coercing to Unicode: need string or buffer, DeviceType found
How should I overcome this problem?
The problem is that you add strings and non-strings together when you construct the email_body_text. Python does not understand what a string together with a device_type is supposed to mean. This is not strange, adding a string and a number has the same problems:
>>> u"abc" + 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: coercing to Unicode: need string or buffer, int found
As you see, the message is exactly the same, except for the fact that here we use an int instead of a DeviceType.
You better solve this by performing string formatting. This is not only more type safe (since it will call unicode(..) and str(..) behind the curtains, but is also more elegant.
But there are other problems with the view as well: for example you use cleaned_data instead of the Request object you constructed in the form, which make things easier, more elegant and readable:
def request_form(request):
if request.method == "POST":
form = RequestForm(request.POST)
if form.is_valid():
post = form.save(commit = False)
post.requested_by_id = request.user
post.save()
user = request.user
useremail = user.email
email_body_text = (
u"Hi {},"
"\nYour requested a repair for the following:"
"\nDevice Type: {}"
"\nUrgency: {}"
"\nRequest must be satisfied by: {}"
"\nReason for new device: {}"
"\nWe will try to complete your request at the earliest."
"In case of any concerns contact the controller of operations."
).format(
user.username,
post.device_type,
post.urgency,
post.request_must_be_satisfied_by,
post.reason,
)
send_mail(email_subject,email_body_text,config.sender_email_id,[useremail],fail_silently=False)
return redirect("NewRequest")
else:
form = RequestForm(initial={'requested_by':request.user})
Tp = DeviceType.objects.all()
Tp = Tp.annotate(text=F('device_type')).values_list('text', flat=True)
print("Tp request form: ", Tp)
Ur = RequestUrgency.objects.all()
Ur = Ur.annotate(text=F('request_urgency')).values_list('text', flat=True)
form.fields["device_type"] = forms.ModelChoiceField(queryset=Tp, empty_label=None)
form.fields["urgency"] = forms.ModelChoiceField(queryset=Ur, empty_label=None)
return render(request, "Form_template.html", {"form": form, "name": "Device Request Form"})
Note; I advice you to migrate to Python-3.x, since Python-2.x will no longer be supported within two years: https://pythonclock.org/
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.
I have an object Task and a form that saves it. I want to automatically asign created_by field to the currently logged in user. So, my view is this:
def new_task(request, task_id=None):
message = None
if task_id is not None:
task = Task.objects.get(pk=task_id)
message = 'TaskOK'
submit = 'Update'
else:
task = Task(created_by = GPUser(user=request.user))
submit = 'Create'
if request.method == 'POST': # If the form has been submitted...
form = TaskForm(request.POST, instance=task)
if form.is_valid():
task = form.save(commit=False);
task.created_by = GPUser(user=request.user)
task.save()
if message == None:
message = 'taskOK'
return tasks(request, message)
else:
form = TaskForm(instance=task)
return custom_render('user/new_task.html',
{'form': form, 'submit': submit, 'task_id':task.id},
request)
The problem is, you guessed, the created_by field doesn't get saved. Any ideas? Thanks
You are creating GPUser, but you don't save it. You must save it first, so it gets pk and only after that it can be assigned to a ForeignKey. Try this:
task.created_by = GPUser.object.create(user=request.user)
or
gpuser = GPUser(user=request.user)
gpuser.save()
task.created_by = gpuser