How can one html form input field update two Django models? - django

I have two models, Region and Room. One room can only be in one region but one region can have many rooms. When the user is creating a room (createRoom view), I want them to enter a region, which should both update the Region model and also assign this Region to the room.
models.py
class Region(models.Model):
region = models.CharField(max_length=200, unique=True)
etc....
class Room(models.Model):
region = models.ForeignKey(Region, on_delete=models.SET_NULL, null=True)
etc....
forms.py
class RoomForm(ModelForm):
class Meta:
model = Room
fields = ['region', etc....]
class RegionForm(ModelForm):
class Meta:
model=Region
fields = ['country', 'region']
views.py
def createRoom(request):
form_room = RoomForm()
form_region = RegionForm()
if request.method == 'POST':
form_room = RoomForm(request.POST)
form_region = RegionForm(request.POST)
if form_room.is_valid() and form_region.is_valid():
room = form_room.save(commit=False)
region = form_region.save(commit=False)
room.region = region.region
room.save()
region.save()
return redirect ('home')
template.html
<form class="form" action="" method="POST">
{% csrf_token %}
{{form_room.media}}
<div class="form__group">
<label for="region_name">Region</label>
{{form_region.region}}
</div>
</form>
The line room.region = region.region in views.py I was hoping would set the region object of the Region model as the region object of the Room model. I receive no errors when submitting the form. However the Room model's region object stays empty.
I updated my views to check for errors and the error seems to be that the forms are not valid:
def createRoom(request):
form_room = RoomForm()
form_region = RegionForm()
if request.method == 'POST':
form_room = RoomForm(request.POST)
form_region = RegionForm(request.POST)
if form_room.is_valid() and form_region.is_valid():
room = form_room.save(commit=False)
region = form_region.save(commit=False)
room.host = request.user
room.region = region.region.pk
region.save()
room.save()
#return redirect ('home')
else:
messages.error(request, 'form not valid')

Im assuming that since you are creating two models in one form, order plays a big role here. You are trying to save room with a ForeignKey region when this model instance does not exist. Try swapping the order:
region.save()
room.save()
That way you first save the Region and then the data in the Room is not empty. Also try using try except: for better error handling. Finally keep in mind that ForeignKey requires a key so you might need to set
room.region = region.pk

Related

Django - This is field is required error

I am new to Django and trying to save some data from the form to the model. I want to insert into two models which have a foreign key constraint relationship (namely Idea and IdeaUpvotes) i.e. from a html template to a view.
My submit code is:
def submitNewIdea(request):
#get the context from the request
context = RequestContext(request)
print(context)
#A HTTP POST?
if request.method == 'POST':
form = submitNewIdeaForm(request.POST)
# Have we been provided with a valid form?
if form.is_valid():
# Save the new Idea to the Idea model
print(request.POST.get("IdeaCategory"))
print(request.POST.get("IdeaSubCategory"))
i = Idea( idea_heading = form["idea_heading"].value()
,idea_description = form["idea_description"].value()
,idea_created_by = form["idea_created_by"].value()
,idea_votes = form["idea_votes"].value()
,idea_category = request.POST.get("IdeaCategory") #value from dropdown
,idea_sub_category = request.POST.get("IdeaSubCategory") #value from dropdown
)
i.save()
# get the just saved id
print(Idea.objects.get(pk = i.id))
iu = IdeaUpvotes(idea_id = Idea.objects.get(pk = i.id)
,upvoted_by = form["upvoted_by"].value()
,upvoted_date = timezone.now() )
iu.save()
form.save(commit = True)
# Now call the index() view.
# The user will be shown the homepage.
return index(request)
else:
# The supplied form contained errors - just print them to the terminal.
print (form.errors)
else:
# If the request was not a POST, display the form to enter details.
form = submitNewIdeaForm()
# Bad form (or form details), no form supplied...
# Render the form with error messages (if any).
return render(request,'Ideas/Index.html',{'form' :form})
form.py --->
class submitNewIdeaForm(forms.ModelForm):
idea_heading = forms.CharField(label = "idea_heading",max_length =1000,help_text= "Please enter the idea heading.")
idea_description= forms.CharField(label = "idea_description",max_length =1000,help_text= "Please enter the idea description.",widget=forms.Textarea)
idea_created_by=forms.CharField(max_length =200, widget = forms.HiddenInput(), initial='wattamw')
idea_votes = forms.IntegerField(widget=forms.HiddenInput(), initial=1)
upvoted_by=forms.CharField(max_length =200, widget = forms.HiddenInput(), initial='abcde')
"""
#commented code
#idea_category_name = forms.CharField(label = "idea_category_name",max_length =250,help_text= "Please select an Idea Category.")
#idea_sub_category = forms.CharField(label = "idea_sub_category",max_length =250,help_text= "Please select an Idea Sub Category.")
idea_category_name = forms.ModelChoiceField(
queryset = IdeaCategory.objects.all(),
widget=autocomplete.ModelSelect2(url='category-autocomplete'))
idea_sub_category = forms.ModelChoiceField(
queryset = IdeaSubCategory.objects.all(),
widget = autocomplete.ModelSelect2(
url='subcategory-autocomplete',
forward = (forward.Field('idea_category_name','id'),)))
"""
class Meta:
model = Idea
fields = ('idea_heading','idea_description','idea_created_by','idea_votes','idea_category_name','idea_sub_category')
class Meta:
model = IdeaUpvotes
fields = ('upvoted_by',)
def __init__(self,*args,**kwargs):
super(submitNewIdeaForm,self).__init__(*args,**kwargs)
self.fields['idea_category_name'] = forms.ModelChoiceField(
queryset = IdeaCategory.objects.all(),
widget=autocomplete.ModelSelect2(url='category-autocomplete'))
self.fields['idea_sub_category'] = forms.ModelChoiceField(
queryset = IdeaSubCategory.objects.all(),
widget = autocomplete.ModelSelect2(
url='subcategory-autocomplete',
forward = (forward.Field('idea_category_name','id'),)))
I am able to print the values and see that they are passed,but I still get the following error :
Error Description
I have removed any foreign key references to the table, the fields are simple character fields.
Please help me out.
Thanks.
In the first place, your form validation is failing. It seems to me that your form template may be wrong.
The second thing is that you don't use Django forms properly. All you need to do to achieve the functionality you are looking for is to use ModelForm and let the form's save method to create the object for you. All you need to do is:
Associate your SubmitNewIdeaForm with the Idea model:
# forms.py
class SubmitNewIdeaForm(ModelForm):
class Meta:
model = Idea
fields = (
'idea_heading',
'idea_description',
'idea_created_by',
'idea_votes',
'idea_category',
'idea_sub_category'
)
Render the form
#form_template.html
<form action="{% url 'your_url' %}" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit" />
</form>
Finally jsut check if the form is valid and call form.save() like so:
def submitNewIdea(request):
if form.is_valid():
form.save()
That's it! I hope that I helped you.
Cheers!
Finished = models.IntegerField('Finished percentage', error_messages={'required':''})
Worked for me.

Django foreign key form save

EDIT: I have since solved the problem (with help from stackoverflow and IRC, but I have to wait two days before I can mark this question as solved. See my reply below to see the solution!
I am currently working an a 'IMDB' clone for me and my friends to use. The idea is that each of us gets an account and can either submit new movies or vote on already submitted ones. These movies are sorted by rating and users can access the detail view to see the individual reviews.
To achieve this I'm using foreign keys in my models.py - one for the movie entries (with information like director, title, etc) and one for the individual votes. The latter one uses foreign keys to fetch the movies title and the user that submitted the review.
However when testing the form that submits reviews I encountered the 'NOT NULL constraint failed: list_vote.voter_id_id' error. When browsing through the error page I discovered that the foreignkey values are not submitted to the database
params: [None, None, 9.0]
query: ('INSERT INTO "list_vote" ("movie_id_id", "voter_id_id", "rating") VALUES (?, ' '?, ?)')
self <django.db.backends.sqlite3.base.SQLiteCursorWrapper object at 0x7f77b8907dc8>
Yet, when I browse the error page for the local vars at the moment of the 'post.save()' command the missing variables seem to be there
entry: <Entry: Starwars 4>
form: <VoteForm bound=True, valid=True, fields=(rating)>
movie_id: 'Starwars 4'
post: <Vote: Vote object>
request: <WSGIRequest: POST '/vote/starwars-4/'>
slug: 'starwars-4'
voter_id : <SimpleLazyObject: <User: admin>>
If I add the ' movie_id' and ' user_id' values to my modelform (in the 'fields' variable) the form actually works. (though this is not what I want, as there could potentially be hundreds of movies in this database, making selection rather difficult. And right now, any user can pretend to be anyone)
I'm probably missing something very obvious, but for the life of me I cannot figure out what I'm doing wrong. The models, forms etc are based on both the Django_girls tutorial (where they work) and the 'Hello WebApp' book (though foreign keys are not used in this book)
Does anyone know what I'm doing wrong here?
These are the models used:
class Entry(models.Model):
movie = models.CharField(max_length=255)
director = models.CharField(max_length=255)
total_votes = models.IntegerField(default=0)
rating = models.FloatField(default=0)
length = models.IntegerField(default=0)
year = models.IntegerField(default=0)
added_by = models.ForeignKey(User)
slug = models.SlugField(unique=True)
def __str__(self):
return self.movie
########################
Vote-model:
########################
class Vote(models.Model):
class Meta:
unique_together = (('voter_id','movie_id'),)
movie_id = models.ForeignKey(Entry)
voter_id = models.ForeignKey(User)
rating = models.FloatField(validators=[MinValueValidator(0), MaxValueValidator(10)])
This is my form.py:
class VoteForm(ModelForm):
class Meta:
model = Vote
fields = ('rating',)
This is the relevant function form the view.py:
def lets_vote(request, slug):
entry = Entry.objects.get(slug=slug)
if request.method == "POST":
form = VoteForm(request.POST)
if form.is_valid():
voter_id = request.user
movie_id = Entry.objects.get(slug=slug).movie
post = form.save(commit=False)
post.save()
return redirect('/movies/')
else:
form = VoteForm()
return render(request, 'list/lets_vote.html', {'entry':entry, 'form': form})
Last but not least: the voteform from the html page:
<form role="form" action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Submit</button>
</form>
{% endblock %}
I posted this question on the Django IRC as well and somebody there helped me solve the problem!
I originally made two mistakes:
I added a '_id' in my models. This is already done so automatically, so the variables in my database were actually called 'movie_id_id'. I've since removed the superfluous '_id'.
The '.movie' in the following line was unnecessary.
post.movie = Entry.objects.get(slug=slug).movie
The correct, working view is as follows:
def lets_vote(request, slug):
entry = Entry.objects.get(slug=slug)
form = VoteForm(request.POST)
if request.method == "POST":
if form.is_valid():
post = form.save(commit=False)
post.voter = request.user
post.movie = Entry.objects.get(slug=slug)
post.save()
return redirect('/movies')
I only needed the actual instance, but the rest of #Alasdair his suggestions were correct.
Thank you #Alasdair and Flobin from IRC!

Multiple form into one view: how to handle a field error?

I have a model like this:
class PersonneTravel(models.Model):
personne = models.ForeignKey(Personne, verbose_name=_(u'Person'))
travel = models.TextField(null=True, blank=True, verbose_name=_(u'Travel'))
date_start = models.DateTimeField(default=None, null=True, blank=True,
editable=True, verbose_name=_(u"Start"))
date_end = models.DateTimeField(default=None, null=True, blank=True,
editable=True, verbose_name=_(u"End"))
comments = models.TextField(null=True, blank=True,
verbose_name=_(u'Comments'))
I display a list of all the travels of one person.
What I wanted to do is to give the possibility to online-edit any of the travels, or to add one, into one view.
Here's how I did:
create a Form: class PersonneTravelForm with all fields
create a view class IndexView(LoginRequiredMixin, generic.FormView):
overload get_context_data where I create all those forms like this:
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context['travels'] = []
for ptf in PersonneTravel.objects.filter(
personne__user=self.request.user,
):
context['travels'].append({
'obj': ptf,
'form': PersonneTravelForm({
'pk': ptf.pk,
'travel': ptf.travel.value,
'date_start': ptf.date_start,
'date_end': ptf.date_end,
'comments': ptf.comments,
})
})
from there in the template, I make a loop:
{% for v in travels %}
{% with v.form as form %}
{% include "my_home/travels/travel_form.html" %}
{% endwith %}{# form #}
{% endfor %}
and in the file travel_form.html I display the form
I've added a bit of javascript with a button "edit" for each form and we the user clicks on it, I slideDown() the form
When the users clicks "update" to update one travel, the corresponding form is sent as a POST. In the form code, I've created all filters in the form def clean_XXXX(self): with all fields properly "cleaned"
In the view, I analyze the fields in def form_valid(self, form):
if there's a pk in the fields it's for update or delete
if all other fields are None I delete
if any field is not None then I update
if there's no pk it's for add a new record, I add one
My big and only problem here is when there's an error: if I try to add an error while checking the form, and there's a problem I do this, for example:
def form_valid(self, form):
fc = form.cleaned_data
d_start = fc['date_start']
d_end = fc['date_end']
comments = fc.get('comments', None)
if d_end and d_start:
if d_end > datetime.datetime.now().date() > d_start:
form.add_error('date_start', _(u"Can't start in the past..."))
form.add_error('date_end', _(u"...and end in the future!"))
return super(IndexView, self).form_invalid(form)
Everything seems fine... except that the error field goes always into the "add new" blank form, never to the good "edit" form. How would you handle this?
You need Django Formsets for this purpose, when you are handling list of forms of a same type. Formset gives you built in features to create new records as well as delete and update older ones.

Django raising error in forms with the hidden field still remain issue

I'm trying to create an application where users can send each other messages.
The function I am working on is called read, allows the user to read the message he receives.
The way my model works in the following manner: Every message is related to a thread and this will used to keep track of replied messages related to each other.
My function works by capturing the message id and filtering all the messages related to the message thread. Then I will populate a form with the current message id and allow the user to reply to the form.
When the user submits via POST, I will retrieve the hidden message id and create a new message using the retrieved message id thread.
The issue: I can't figure out how to raise an error for such situation when exceeding the character limit and populating the current message id with the raised error. Can someone kindly help me?
class Person(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=30, blank=True)
def __unicode__(self):
return self.user.username
class Thread(models.Model):
subject = models.CharField(max_length=100, blank=True)
user = models.ForeignKey(User)
class Message(models.Model):
user = models.ForeignKey(User, related_name='sender')
recipient = models.ForeignKey(User, related_name='recipient')
created = models.DateTimeField(auto_now_add=True)
body = models.CharField(max_length=1000)
read = models.BooleanField(default=False)
sentmessage = models.BooleanField(default=False)
thread = models.ForeignKey(Thread)
draft = models.BooleanField(default=False)
def __unicode__(self):
return self.body
Views:
#login_required
def read(request,id):
try:
messages = Message.objects.get(pk=id,recipient=request.user)
except Message.DoesNotExist:
return HttpResponseRedirect(reverse('world:Display'))
messages.read = True
messages.save()
if request.method =='POST':
form = ReplyForm(request.POST)
if form.is_valid():
id = request.POST.get('hidden_field', False)
try:
messages = Message.objects.get(pk=id ,recipient=request.user,sentmessage=True,draft=False)
except Message.DoesNotExist or Thread.DOesNotExist:
return HttpResponseRedirect(reverse('world:LoginRequest'))
person = Person.objects.get(user=messages.user)
if person.inbox == "D":
return HttpResponseRedirect(reverse('world:message'))
body = form.cleaned_data['body']
Message.objects.create(user=request.user,recipient=messages.user,body=body,thread=messages.thread,sentmessage=True,read=False)
return HttpResponseRedirect(reverse('world:message'))
message = Message.objects.filter(thread=messages.thread ).filter(created__lte=messages.created)
person = Person.objects.get(user=request.user)
initial = {}
initial.update({'hidden_field': messages.id})
form = ReplyForm(initial=initial)
return render(request,'read.html',{'messages':messages,'form':form,'message':message,'person':person})
forms
class ReplyForm(forms.Form):
body = forms.CharField(widget=forms.Textarea,required=False,max_length=555)
hidden_field = forms.CharField(widget=forms.HiddenInput())
Template:
<div class="box22">
{% for m in message %}
<div class="wrapper">
<div class="user">{{m.user.username}} </div>
<div class="message">{{m.body}}</div>
</div>
{% endfor %}
<form method="POST" >{% csrf_token %}
{{form.body}}{{form.hidden_field}}
<input type = "submit" value= "send" class="sen"/>
</form>
{{form.body.errors}}
First of all, I recommend you use only form.cleaned_data to get submited data.
id = form.cleaned_data['hidden_field']
Also, why you reassign id from request.POST data when you already have id defined in your function args? Maybe it should look like:
msg_response_id = form.cleaned_data['hidden_field']
Then, you always need to check hidden_field value and you can do it with your custom clean method. Add clean_hidden_field method to your form and also you should override init form method and pass id and msg_response_id.
In your views:
form = ReplyForm(initial=initial, id=id, msg_response_id=msg_response_id)
And forms:
class ReplyForm(forms.Form):
def __init__(self, *args, **kwargs):
self.id = kwargs.pop('id', None)
self.msg_response_id = kwargs.pop('msg_response_id', None)
super(ReplyForm, self).__init__(*args, **kwargs)
hidden_field = forms.CharField(widget=forms.HiddenInput())
def clean_hidden_field(self):
if self.id and self.msg_response_id and self.id == self.msg_response_id:
# Raise form error, which will appear in your template.
raise forms.ValidationError(_('You can't reply to your own message.'))
The last paragraph describing what you want is not all too clear, but I believe what you are trying to do, is make an raise an exception when a certain requirement is not met in your form.
If that is the case, then simply catching an ValidationError which is inbuilt into django will do the trick.
So, something like this:
try:
form = ReplyForm(request.POST)
except ValidationError:
# do what you want here
Alternatively,
form = ReplyForm(request.POST)
if form.is_valid():
# do stuff
else:
raise ValidationError
If this does not work for you, then you might try validation the post-data without using django-forms, but usually django takes care of this problem for you, and it will automatically generate the error messages. You can take a deeper look # form validation here.
However if you want even more fine grain control you can make the form yourself, and handle the validation within your views. Otherwise, ValidationError should do the trick and let you redirect the error messages.

Django and users entering data

I am building a webapp which will be used by a company to carry out their daily operations. Things like sending invoices, tracking accounts receivable, tracking inventory (and therefore products). I have several models set up in my various apps to handle the different parts of the web-app. I will also be setting up permissions so that managers can edit more fields than, say, an office assistant.
This brings me to my question. How can I show all fields of a model and have some that can be edited and some that cannot be edited, and still save the model instance?
For example, I have a systems model for tracking systems (we install irrigation systems). The system ID is the primary key, and it is important for the user to see. However, they cannot change that ID since it would mess things up. Now, I have a view for displaying my models via a form using the "form.as_table". This is efficient, but merely spits out all the model fields with input fields filling in the values stored for that model instance. This includes the systemID field which should not be editable.
Because I don't want the user to edit the systemID field, I tried making it just a label within the html form, but django complains. Here's some code:
my model (not all of it, but some of it):
class System(models.Model):
systemID = models.CharField(max_length=10, primary_key=True, verbose_name = 'System ID')
systemOwner = models.ForeignKey (System_Owner)
installDate = models.DateField()
projectManager = models.ForeignKey(Employee, blank=True, null=True)
#more fields....
Then, my view for a specific model instance:
def system_details(request, systemID):
if request.method == 'POST':
sysEdit = System.objects.get(pk=systemID)
form = System_Form(request.POST, instance=sysEdit)
if form.is_valid():
form.save()
return HttpResponseRedirect('/systems/')
else:
sysView = System.objects.get(pk=systemID)
form = System_Form(instance=sysView)
return render_to_response('pages/systems/system_details.html', {'form': form}, context_instance=RequestContext(request))
Now the html page which displays the form:
<form action="" method="POST">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Save Changes">
<input type="button" value="Cancel Changes" onclick="window.location.href='/systems/'">
</form>
So, what I am thinking of doing is having two functions for the html. One is a form for displaying only those fields the user can edit, and the other is for just displaying the content of the field (the systemID). Then, in the view, when I want to save the changes the user made, I would do:
sysValues = System.objects.get(pk=SystemID)
form.save(commit = false)
form.pk = sysValues.sysValues.pk (or whatever the code is to assign the sysValues.pk to form.pk)
Is there an easier way to do this or would this be the best?
Thanks
One thing you can do is exclude the field you don't need in your form:
class System_Form(forms.ModelForm):
class Meta:
exclude = ('systemID',)
The other is to use read-only fields: http://docs.djangoproject.com/en/1.3/ref/contrib/admin/#django.contrib.admin.ModelAdmin.readonly_fields as #DTing suggessted
To make a field read only you can set the widget readonly attribute to True.
using your example:
class System_Form(ModelForm):
def __init__(self, *args, **kwargs):
super(System_Form, self).__init__(*args, **kwargs)
self.fields['systemID'].widget.attrs['readonly'] = True
class Meta:
model = System
or exclude the fields using exclude or fields in the class Meta of your form and display it in your template if desired like so:
forms.py
class System_Form(ModelForms):
class Meta:
model = System
exclude = ('systemID',)
views.py
def some_view(request, system_id):
system = System.objects.get(pk=system_id)
if request.method == 'POST':
form = System_Form(request.POST, instance=system)
if form.is_valid():
form.save()
return HttpResponse('Success')
else:
form = System_Form(instance=system)
context = { 'system':system,
'form':form, }
return render_to_response('some_template.html', context,
context_instance=RequestContext(request))
some_template.html
<p>make changes for {{ system }} with ID {{ system.systemID }}</p>
<form method='post'>
{{ form.as_p }}
<input type='submit' value='Submit'>
</form>