So I'm building a basic Q&A site-- Each topic has a series of questions associated with it, and each question has multiple answers associated with it.
I'm creating the user input for questions and they have to associated with a topic. This is the questions model
#models.py
class Question(models.Model):
movie = models.ForeignKey(Movie, blank=True, null=True)
question_text = models.CharField(max_length = 1000)
question_detail = models.CharField(max_length = 5000, blank = True, null = True)
q_pub_date = models.DateTimeField(auto_now_add = True)
q_author = models.ForeignKey(User)
class QuestionForm(ModelForm):
def save(self, user = None, force_insert = False, force_update = False, commit = True):
q = super(QuestionForm, self).save(commit = False)
q.q_author = user
if commit:
q.save()
return q
class Meta:
model = Question
exclude = ('movie', 'q_author', 'q_pub_date')
This is the URL conf
#urls.py
url(r'^(?P<movie_id>\d+)/add_question/$', 'add_question'),
Now here is the view
#views.py
def add_question(request, movie_id):
if request.method == "POST":
form = QuestionForm(request.POST, request.FILES)
#QuestionForm.movie = Movie.objects.get(pk = movie_id)
if form.is_valid():
form.save(user = request.user)
return HttpResponseRedirect("/home/")
else:
form = QuestionForm()
return render_to_response("qanda/add_question.html", {'form': form}, context_instance = RequestContext(request))
This is the HTML code
#add_question.html
<h1> Add Question: {{ user.username }}</h1>
<form action = "" method = "post">{% csrf_token %}
{{ form.as_p }}
<input type = "submit" value = "Ask" />
<input type = "hidden" name = "next" value = "{{ next|escape }}" />
</form>
In the view, the commented out line is what I added to the view to try and auto save the model. When adding a question, the URL has the ID of the movie it is associated with, and my thought is to take that ID and then plug it into the ForeignKey to identify which movie is associated with the question. However, when I use my code, it changes all of the Questions' movie associations to the current movie instead of just changing that specific question's movie association. Without the code, it doesn't associate a Movie with the Question at all. How do I fix this?
Use this:
#views.py
def add_question(request, movie_id):
if request.method == "POST":
form = QuestionForm(request.POST, request.FILES)
if form.is_valid():
question = form.save(user = request.user)
question.movie = Movie.objects.get(pk = movie_id)
question.save()
return HttpResponseRedirect("/home/")
else:
form = QuestionForm()
return render_to_response("qanda/add_question.html", {'form': form}, context_instance = RequestContext(request)
For question asked in comment
You should avoid using absolute URLs in views or templates. Consider a scenario, where you decide to change home URL from /home/ to /myhome/. You will have to edit it where ever you have used them. It is always better to name the urls (docs):
# URL Conf
url(r'^home/$', 'home_view', name="home_url"),
url(r'^(?P<movie_id>\d+)/add_question/$', 'add_question', name="add_question_url"),
url(r'^home/(?P<movie_id>\d+)/$', 'movie_view', name="movie_url"),
The name argument act as an unique identifier to your actual URLs
Now in you views:
from django.core.urlresolvers import reverse
def some_view(request):
...
return HttpResponseRedirect(reverse('home_url'))
Now what ever change you make to the URL (say /home/ to /myhome/ makes no effect to the view as long as the name argument has the same value in the URL conf.
If you wish to pass parameters (like movie_id in your case)
def some_view(request, movie_id):
...
return HttpResponseRedirect(reverse('movie_url', kwargs={'movie_id':movie_id}))
The same concept should be used in templates to avoid hard-coding URLS in templates. Please read this for more details
Related
I'm trying make a comment section for my Q&A project.I made the model for comment , the form part in question_detail.html an , also the QuestionCommentForm() in form.py .
model.py
class QuestionComment(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE)
question =
models.ForeignKey(Question,on_delete=models.CASCADE)
created_date = models.DateTimeField(auto_now_add= True)
body = models.CharField(max_length=200, null= True ,
blank = True, default ='')
def __str__(self):
return str(self.body)
forms.py
{%if c_form%}
<form method="POST" action= "{% usl 'blog:question-commet' question.id >{% csrf_token %}
{{c_form.media}}
{{ c_form.as_p}}
<button type = "submit" , name = "question_id", value = "{{question.pk}}", class ="btn btn-secondary btn-sm">submit comment</button>
views.py
#api_view(['POST','GET'])
def question_comment(request, *args, **kwargs):
form = QuestionCommentForm()
print('finction comment started'*20)
if request.method == 'POST':
c_form = QuestionCommentForm(request.POST)
if c_form.is_valid():
new_comment = c_form.save(commit=False)
new_comment.refresh_from_db()
c_form.instance.user = request.user
question = Question.objects.get(id = request.POST.get('question_id')
new_comment.question = question
new_comment.bldy =c_form.cleaned_data.get('body')
new_comment.save()
context['c_form'] = c_form
return render(request, 'blog/question_detail.html',context)
class QuestionDetail(DetailView):
template_name = 'blog/question_detail.html'
model = Question
context_object_name = 'question'
count_hit = True
def get_queryset(self):
return Question.objects.filter(id = self.kwargs['pk'])
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
c_form = QuestionCommentForm()
context = super(QuestionDetail,self).get_context_data(**kwargs)
self.obj= get_object_or_404(Question, id = self.kwargs['pk'])
self.object.save()
self.object.refresh_from_db()
answers = Answer.objects.filter (question_id = self.obj.id).order_by('-created_date')
liked =self.obj.like.filter(id =self.request.user.id).exists()
print('liked in class question not checked still' *10)
comments= QuestionComment.objects.filter(question = self.kwargs['pk'])
context['comments']= comments
context["answers"]=answers
context["liked "] = liked
context['c_form'] = c_form
return context
def post(request, *args, **kwargs):
print('post started'*100)
c_form = QuestionCommentForm()
c_form = QuestionCommentForm(request.POST)
if c_form.is_valid():
new_comment = c_form.save(commit=False)
new_comment.refresh_from_db()
new_comment.user = request.user
new_comment.question = c_form.cleaned_data.get('question_id')
new_comment.bldy =c_form.cleaned_data.get('body')
new_comment.save()
context['c_form'] = c_form
else:
c_form= QuestionCommentForm()
return render(request, 'blog/question_detail.html',context)
url.py
...
path('question-comment/<int:pk>/', question_comment, name = 'question-comment'),
]
in my view, first I tried to use a another function to handle the comment, got no result and made a def post()
in the class QuestionDetial() , and still the form shows up but when I type something and hit the button , it refresh the page and nothing saves . I have already saved a comment using admin and it appears in the question-detail page. used print to find the bug, but it seems the post() in class and the question_comment() not being recall. searched a lot but no answer. (BTW I get no error except the NoReverseMatch that I fixed)
You never save the model object (not by the form, nor by the view). Furthermore the name of the method is 'POST', not 'Post':
#api_view(['POST','GET'])
def question_comment(request, *args, **kwargs):
form = QuestionCommentForm()
print('finction comment started'*20)
if request.method == 'POST':
c_form = QuestionCommentForm(request.POST)
if c_form.is_valid():
c_form.instance.user = request.user
c_form.save()
context = {'c_form': c_form }
return render(request, 'blog/question_detail.html',context)
ok , the problem was that I had two form-tags in question_detail template and buy adding comment form in the end of the template had 3 , but the problem was when I pressed the submit comment button the terminal showed this message
"POST /create-like/blog/answer/18/ HTTP/1.1" 302 0
which was the form I had made for like function .(while my url was question-comment)
I accidentally forgot to close the like-form tag in template. so all I needed was putting a
</form>
after the form block .
thank's Willem Van Onsem. it solved my post method error.
and these were very helpful
Proper way to handle multiple forms on one page in Django
How can I build multiple submit buttons django form?
I am very new to Python and Django and am stuck with this problem , which I think should be very simple to solve.
model.py
class UserDetails(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
billingAddress = AddressField(related_name='+',blank =True ) # Used django-address https://pypi.org/project/django-address/
shippingAddress = AddressField(related_name='+',blank =True)
forms.py
class AddressForm(forms.ModelForm):
class Meta:
model = UserDetails
exclude = ['user']
views.py
def address(request):
form = AddressForm(request.POST or None)
if request.method == 'POST' and form.is_valid():
zipCode = request.POST.get("ZipCode","")
form = AddressForm(data=request.POST)
detailForm = form.save(commit = False)
detailForm.user = request.user
baddressDict = {'raw':request.POST.get("billingAddress","")+", " + zipCode, 'postal_code': zipCode,}
saddressDict = {'raw':request.POST.get("shippingAddress","")+", " + zipCode, 'postal_code': zipCode,}
detailForm.billingAddress = baddressDict
detailForm.shippingAddress = saddressDict
detailForm.save()
else:
form = AddressForm()
return render(request,'showcase/address.html',{'form': form})
address.html
<form action="." method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="text" name="ZipCode" value="Vip Code" >
<input type="submit" value="Submit" >
</form>
What I am trying to do it update the shipping & Billing address for current user.
The first time I am doing this it works but the second time it gives
UNIQUE constraint failed: showcase_userdetails.user_id
which obviously is cause it trying to add another row in the DB.
How do i make sure it updates and not insert?
Thanks,
Gourav
Quite simply, you have to pass an existing instance of your model:
def edit_address(request):
user = request.user
try:
address_instance = UserDetail.objects.get(user=user)
except UserDetail.DoesNotExist:
address_instance = None
if request.method == 'POST':
form = AddressForm(request.POST, instance=address_instance)
if form.is_valid():
details = form.save(commit=False)
# You should really let the form takes care of all this,
# and you should DEFINITLY NOT use unsanitized data from
# request.POST - the whole point of forms is to make sure
# your user inputs are properly sanitized...
details.user = request.user
# etc
else:
# passing it for the GET part too so the user
# can see the already existing data (if any)
form = AddressForm(instance=address_instance)
return render(request,'showcase/address.html',{'form': form})
I tried creating model form to add new record to my models in django, but I got this error:
The view post.views.add_job_resume didn't return an object. It returned None instead.
this is my files(Notice that in this file I have more codes than what I write here):
view.py
def add_job_resume(request):
if request.method=="POST":
form = AddJobForm(request.POST)
if form.is_valid():
job_resume = form.save(commit=False)
job_resume.user= request.user
job_resume.save()
return redirect('view_job_resume')
else:
form = AddJobForm()
return render(request, 'education/job_research_education_records/job_resume/add_job_resume.html', {'form': form})
forms.py
class AddJobForm(ModelForm):
class Meta:
model = JobRecord
fields = [
'title',
'explains',
'post',
'organization',
'time_start',
'time_end',
'upload_url',
]
models.py
class JobRecord(models.Model):
user = models.ForeignKey(User, on_delete=models.PROTECT)
title = models.TextField(max_length=250, blank=False)
explains = models.TextField(max_length=500, blank=True)
post = models.TextField(max_length=100, blank=False)
organization = models.TextField(max_length=100, blank=False)
time_start = models.TextField(max_length=100)
time_end = models.TextField(max_length=100)
upload_url = models.FileField(upload_to='job-resume-files/')
def __str__(self):
return self.title
add_job_resume.html
<form method="post" >
{% csrf_token %}
{{form.as_p }}
<button type="submit" class="btn btn-info">add</button>
</form>
urls.py
urlpatterns = [
path('edu/resume/job/', views.view_job_resume, name='view_job_resume'),
path('edu/resume/job/add', views.add_job_resume, name='add_job_resume')]
I search a lot for this error but I can't solve that! What is going on?
This has nothing to do with the redirect(..) itself. You just forgot a code path. It is possible that the method is indeed POST, but that the form is not valid. So the condition in the first if is satisfied, but the condition in the second if is not. Right now, your view will return nothing (hence return None) for this situation.
It is common to simply rerender the template, but now with the invalid form, such that the form can show the errors, like:
def add_job_resume(request):
if request.method=="POST":
form = AddJobForm(request.POST)
if form.is_valid():
job_resume = form.save(commit=False)
job_resume.user= request.user
job_resume.save()
return redirect('view_job_resume')
else:
form = AddJobForm()
# Not placed under the else
return render(
request,
'education/job_research_education_records/job_resume/add_job_resume.html',
{'form': form}
)
Notice that the render is not put under the else part. Indeed regardless whether the method is POST and the form is invalid, or the method is not POST, we render the page and respond with the rendered page.
Redirecting is not the problem. The problem is that your form is not valid, and the view does not turn anything if that happens
You should unindent the last line one level, so that it catches this case.
Can someone help me with fixing Django ModelForm?
This particular code can add new item to database as expected, but when I'm trying to edit db record - It just add new record, instead of updating old. I'm quite new in Django framework.
views.py:
def manage(request, item_id = None):
t = get_object_or_404(Hardware, id=item_id) if item_id else None
form = Manage(request.POST or None, instance=t)
if t:
if form.is_valid():
#form.save()
hostname = form.cleaned_data['hostname']
cpu = form.cleaned_data['cpu']
os = form.cleaned_data['os']
ram = form.cleaned_data['ram_total']
storage = form.cleaned_data['storage']
hostdata = Hardware(
hostname=hostname,
cpu=cpu,
ram_total=ram,
os=os,
storage=storage,
lock_state=t.lock_state, # because in edit operation we shouldn't change it.
lock_date=t.lock_date, # because in edit operation we shouldn't change it.
locked_by=t.locked_by) # because in edit operation we shouldn't change it.
hostdata.save()
return HttpResponseRedirect(reverse('main:index'))
elif not t:
if form.is_valid():
hostname = form.cleaned_data['hostname']
cpu = form.cleaned_data['cpu']
os = form.cleaned_data['os']
ram = form.cleaned_data['ram_total']
storage = form.cleaned_data['storage']
current_user = request.user
user = User.objects.get(id=current_user.id)
hostdata = Hardware(
hostname=hostname,
cpu=cpu,
ram_total=ram,
os=os,
storage=storage,
lock_state=0,
lock_date=datetime.datetime.now(),
locked_by=user)
hostdata.save()
return HttpResponseRedirect(reverse('main:index'))
return render(request, 'hardware/edit.html', {'form': form})
models.py:
class Hardware(models.Model):
hostname = models.CharField(max_length=255, default=None)
os = models.CharField(max_length=255, default=None)
cpu = models.CharField(max_length=255, default=None)
ram_total = models.CharField(max_length=255, default=None)
storage = models.CharField(max_length=255, default=None)
lock_state = models.BooleanField(default=0)
locked_by = models.ForeignKey(User)
lock_date = models.DateTimeField(default=None)
alive = models.BooleanField(default=0)
class Meta:
db_table = "hardware"
def __str__(self):
return self.hostname
forms.py:
class Manage(forms.ModelForm):
class Meta:
model = Hardware
fields = ['hostname', 'os', 'cpu', 'ram_total', 'storage']
urls.py:
url(r'^manage/new/$', views.manage, name='add'),
url(r'^manage/edit/(?P<item_id>[0-9]+)/$', views.manage, name='edit')
template:
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Save!" />
</form>
You already retrieved the instance t in the first line of your view. The code below will always create a new instance (unless you specify the pk parameter):
hostdata = Hardware(...)
hostdata.save()
Simply do this instead:
if t:
if form.is_valid():
t.hostname = form.cleaned_data['hostname']
t.cpu = form.cleaned_data['cpu']
....
t.save()
However, you really should rely on the save method provided by the ModelForm as the other answers suggested. Here's an example:
def manage(request, item_id=None):
t = get_object_or_404(Hardware, id=item_id) if item_id else None
# if t is None, a new object will be created in form.save()
# if t is an instance of Hardware, t will be updated in form.save()
form = Manage(request.POST, instance=t)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('main:index')
return render(request, 'hardware/edit.html', {'form': form})
You also specified fields in your form:
fields = ['hostname', 'os', 'cpu', 'ram_total', 'storage']
These are the fields which will be set or updated when you call form.save().
I think something like this - using update_fields - should work:
def manage(request, item_id = None):
t = get_object_or_404(Hardware, id=item_id)
form = Manage(request.POST or None, instance=t)
if t:
if form.is_valid():
#form.save()
t.hostname = form.cleaned_data['hostname']
t.cpu = form.cleaned_data['cpu']
t.os = form.cleaned_data['os']
t.ram = form.cleaned_data['ram_total']
t.storage = form.cleaned_data['storage']
t.save(update_fields=['hostname', 'cpu', 'os','ram','storage'])
return HttpResponseRedirect(reverse('main:index'))
........
Try Class Based View, which in it's simplest looks like:
from django.views import generic
class HardwareEditView(generic.UpdateView):
template_name = "hardware.html"
form_class = Manage
You will have to add get_absolute_url to the model.
Generic class based views are exactly for this standard create/update/view common tasks.
I have a model Attribute and Product, declared like this:
class Attribute(models.Model):
value = models.TextField()
owner = models.ForeignKey(User)
type = models.ForeignKey(AttributeType)
image = ImageField(upload_to='attributes', null=True, blank=True)
related_attribute = models.ManyToManyField('self', blank = True, null = True)
class BaseWorkspace(models.Model):
name = models.CharField(max_length=255)
owner = models.ForeignKey(User)
attributes = models.ManyToManyField('Attribute', blank = True, null = True)
created = CreationDateTimeField()
modified = ModificationDateTimeField()
comments = models.ManyToManyField('Comment', blank = True, null = True )
sort_order = models.IntegerField(blank = True)
class Product(BaseWorkspace):
project = models.ForeignKey('Project', related_name='products')
how can I establish m-m relationship using formsets? I have tried model formset factories like this:
AttributeFormset = modelformset_factory(Attribute, form=AttributeForm)
with this function in the generic view:
def form_valid(self, form):
f = form.instance
f.sort_order = Product.default_sort_order()
f.owner = self.request.user
f.project = get_object_or_404(Project, pk=self.kwargs['pk'])
context = self.get_context_data()
attribute_form = context['attribute_form']
if attribute_form.is_valid():
self.object = form.save()
attribute_form.instance = self.object
attribute_form.save()
return HttpResponseRedirect(reverse(self.get_success_url()))
else:
return self.render_to_response(self.get_context_data(form=form))
but I cannot get it to work. any ideas?
Try something like this:
from django.forms.models import modelformset_factory
def my_view_function(request) :
# not sure where the product whose formset we are working on comes from
product = <whatever>
AttributeFormSet = modelformset_factory(Attribute)
if request.method == "POST" :
# POST bound formset
formset = AttributeFormSet(request.POST, queryset=Attribute.objects.filter(product=product))
# If the entire formset is valid
if formset.is_valid() :
for form in formset:
# Save each form in the set
b = form.save()
else :
#There was an error (add a message using the messages framework?)
pass
else :
# initial formset w/o post
formset = AttributeFormSet(queryset=Attribute.objects.filter(product=product))
...
Its kind of hard to give you more specific answer, I think we would need the entire view function or view class if you are using class based views.
In your template, something as simple as this (from the docs) should do it.
<form method="post" action="">
{{ formset.management_form }}
<table>
{% for form in formset %}
{{ form }}
{% endfor %}
</table>
</form>
If you need the ability to add forms to the formset at runtime w/ javascript look at this: http://code.google.com/p/django-dynamic-formset/. Ive never used it, but at the very least it looks like a step in the correct direction.
EDIT
First exclude product from the formset
AttributeFormSet = modelformset_factory(Attribute, exclude=('product',))
then change the form processing block to not commit on save, and manually attach the product.
if formset.is_valid() :
for form in formset:
# get this form's instance
b = form.save(commit=False)
# attach product
b.product = product
# save the instance
b.save()
By using f = form.instance you access the original instance. If the attribute_form is valid you call the save method on the form, instead of the f. All the changes you did on f will be lost.
Have a look at saving-objects-in-the-formset how to update instances of a formset before saving them.