One form for two models - django

UPDATE The issue is solved, all the code you can see works.
Hello!
I have a ForeignKey relationship between TextPage and Paragraph and my goal is to make front-end TextPage creating/editing form as if it was in ModelAdmin with 'inlines': several fields for the TextPage and then a couple of Paragraph instances stacked inline. The problem is that i have no idea about how to validate and save that:
#login_required
def textpage_add(request):
profile = request.user.profile_set.all()[0]
if not (profile.is_admin() or profile.is_editor()):
raise Http404
PageFormSet = inlineformset_factory(TextPage, Paragraph, fields=('title', 'text', ), extra=5)
textpage = TextPage()
if request.POST:
textpageform = TextPageForm(request.POST, instance=textpage, prefix='page')
formset = PageFormSet(request.POST, instance=textpage, prefix='paragraphs')
# Saving data
if textpageform.is_valid():
textpageform.save()
if formset.is_valid():
formset.save()
return HttpResponseRedirect(reverse(consult_categories))
else:
textpageform = TextPageForm(instance=textpage, prefix='page')
formset = PageFormSet(instance=textpage, prefix='paragraphs')
return render_to_response('textpages/manage.html', { 'formset' : formset,
'textpageform' : textpageform,
}, context_instance=RequestContext(request))
I know it's a kind of code-monkey style to post code that you don't even expect to work but I wanted to show what I'm trying to accomplish. Here is the relevant part of models.py:
class TextPage(models.Model):
title = models.CharField(max_length=100)
page_sub_category = models.ForeignKey(PageSubCategory, blank=True, null=True)
def __unicode__(self):
return self.title
class Paragraph(models.Model):
article = models.ForeignKey(TextPage)
title = models.CharField(max_length=100, blank=True, null=True)
text = models.TextField(blank=True, null=True)
def __unicode__(self):
return self.title
Any help would be appreciated. Thanks!
UPDATE. Instance references added, but still doesn't work - results in a ValidationError on this string:
formset = PageFormSet(request.POST, instance=textpage, prefix='paragraphs')
Any ideas?

The updated code with instance references actually works fine! The problem was in the template: I forgot the ManagmentForm. Here is the template code:
{% extends "site_base.html" %}
{% block body %}
<form action="" method="post">
{{ textpageform.as_p }}
{{ formset.management_form }}
{% for form in formset.forms %}
<p>{{ form.as_p }}</p>
{% endfor %}
<input type="submit" value="Go" />
{% endblock %}
Hope this example helps newbies like me :)

Related

NOT NULL constraint failed: social_media_app_blogcomment.user_id

I'm making this comment system for my blogs.. I already made the model, the ModelForm and the view to display the comments and the blog. I'm just really confused how to save the comments related to a specific blog. I tried to save the comments with a view but I face an IntegrityError. A little help would be appreciated.
Here's my views.py:
#login_required #View to show the blogs and comments related to it
def readblog(request, blog_pk):
Blog = get_object_or_404(blog, pk=blog_pk)
return render(request, 'social_media/readblog.html', {'Blog':Blog,'Form':CommentForm()})
#login_required #view to save the comments
def commentblog(request,blog_pk):
Blog = get_object_or_404(blog,pk=blog_pk)
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
Form = form.save(commit=False)
Form.Blog = Blog
Form.save()
return redirect('usersfeed')
Urls.py:
path('commentblog/<int:blog_pk>', views.commentblog, name='commentblog'),
path('readblog/<int:blog_pk>', views.readblog, name='readblog'),
HTML Page to write and save comments (along with the blog):
{{ Blog.title }}
<br>
{{ Blog.text }}
<br>
{% if Blog.image %}
<img src="{{ Blog.image.url }}" alt="">
{% endif %}
<br>
<form action="{% url 'commentblog' Blog.id %}" method="post">
{% csrf_token %}
{{ Form.as_p }}
<button type="submit">Comment!</button>
</form>
{% for i in Blog.BlogComment.all %}
{{ i.comment }}
<b>user:{{ i.user }}</b>
<br>
{% endfor %}
Comment's model:
class BlogComment(models.Model): # --run-syncdb <- (Research about this!)
user = models.ForeignKey(User, on_delete=models.CASCADE)
comment = models.CharField(max_length=250, null=True)
blog = models.ForeignKey(blog, related_name='BlogComment', on_delete=models.CASCADE, blank=True, null=True)
def __str__(self):
return self.comment
Forms.py:
class CommentForm(forms.ModelForm):
class Meta:
model = BlogComment
fields = ['comment']
You need to add the user since it's a not null field in your model:
def commentblog(request,blog_pk):
blog_obj = get_object_or_404(blog,pk=blog_pk)
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
form_obj = form.save(commit=False)
form_obj.blog = blog_obj
# add user instance
form_obj.user = request.user
form_obj.save()
return redirect('usersfeed')

What happens in the background when you submit a class based view?

I'm using a class based generic view, specifically the UpdateView. I have other objects that I have currently which does CRUD as expected.
Now, I created a new object which also uses the generic class based views. My issue right now is that when I submit an update form, I can't exactly trace what happens when I press that submit button on an update form.
it is pointing to go on this url
http://localhost:8000/order/question_update/
but I don't have a question_update url that doesn't have a parameter. I have a question_update url that has a parameter at the end.
from my urls.py
path('question_update/<int:pk>', views.QuestionUpdate.as_view(), name='question_update'),
and also the success url is not pointing to the question_update url above.
How do I know what does the generic class UpdateView does when I hit the submit button? I'd just like to trace where it's getting the URL which I did not declared anywhere.
I did a text search on my codes and this url is not declared at all.
here are the other URLs
path('question_list/<int:file_id>/', views.QuestionList.as_view(), name='question_list'),
path('question_create_param/<int:file_id>/', views.QuestionCreate.as_view(), name='question_create_param'),
path('question_update/<int:pk>', views.QuestionUpdate.as_view(), name='question_update'),
here's my view for update function
class QuestionUpdate(LoginRequiredMixin, UpdateView):
login_url = 'login'
model = Question
form_class = QuestionForm
def get_form(self, data=None, files=None, **kwargs):
kwargs['pk'] = self.kwargs['pk']
return QuestionForm(data, files, **kwargs)
def get_success_url(self):
messages.success(self.request, 'Question updated')
obj = get_object_or_404(Question, pk=self.kwargs['pk'])
return reverse_lazy('file_status:question_list', args=[obj])
and this is my template
{% extends "base.html" %}
{% load bootstrap3 %}
{% block content %}
<h1>Create/Update</h1>
<form id="question_form" method="post" action="." class="form">
{% csrf_token %}
{% bootstrap_form form layout='vertical' %}
{% buttons %}
<button type="submit" class="btn btn-primary">Submit</button>
{% endbuttons %}
</form>
{% endblock %}
here's my model as well
class Question(models.Model):
file = models.ForeignKey(file, on_delete=models.CASCADE)
query_type = models.CharField(max_length=255, null=True)
date_sent = models.DateField(auto_now=False, auto_now_add=False, null=True)
date_completed = models.DateField(auto_now=False, auto_now_add=False, null=True)
responsible = models.CharField(max_length=255, null=True)
status = models.CharField(max_length=255, null=True)
remarks = models.CharField(max_length=255, null=True)
def __str__(self):
return str(self.file.id)
def get_absolute_url(self):
return reverse('file_status:question_detail', args=[str(self.id)])
It appears some tags in the form should not be there in the first place. What I did was get back to the documentation in django projects and identify how it should be declared properly.
My form starts with this:
<form id="question_form" method="post" action="." class="form">
But it should only be this:
<form id="question_form" method="post">
I'm not entirely sure why action and class tags are causing errors on submit of an update form, if you know please enlighten me. Another weird thing is that I have those 2 extra tags on my other forms but it only happens to fail on the UpdateView for the question object.
Also this was patterned from cookiecutter django crud.

Unable to find the solution for this error : Django: django.db.utils.IntegrityError: NOT NULL constraint failed: book_comment.related_post_id

In short:
I have created a Post model and Comment model and created a comment form, I am serving a single url which will show all posts, related comment and a comment form to enter new comments. With a submission page is reloaded with new comments. But when I submit the comment I get the error:
django.db.utils.IntegrityError: NOT NULL constraint failed: book_comment.related_post_id
This is one answer that looked promising but I am unable to do something.
I think it is not getting parent post id.
Long Version:
This is my model File:
def user_image_path(instance, filename):
return f"profile/user_{random.randint(1,1000)}_{filename}"
class Post(models.Model):
post_title = models.CharField(max_length=250)
post_creator = models.CharField(max_length=150)
creator_pic = models.ImageField(upload_to=user_image_path)
post_body = models.TextField()
post_created = models.DateTimeField(auto_now_add=True)
post_updated = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.post_title} **{self.post_creator}**"
class Comment(models.Model):
related_post = models.ForeignKey(Post, related_name="comments")
comment_creator = models.CharField(max_length=150)
comment_body = models.CharField(max_length=1024)
comment_created = models.DateTimeField(auto_now_add=True)
comment_updated = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.comment_creator}"
This is my form:
from django import forms
from .models import Post, Comment
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['comment_creator', 'comment_body']
This is views:
from django.shortcuts import render, HttpResponseRedirect,reverse
from .models import Comment, Post
from .forms import CommentForm
# Create your views here.
def servePage(request):
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
form.save()
HttpResponseRedirect(reverse('serve'))
else:
form = CommentForm()
posts = Post.objects.all()
return render(request, 'book/showpost.html', {'posts': posts, 'form': form})
This is my html template:
{% extends 'book/base.html' %}
{% block content %}
<h1>Welcome to book of life</h1>
<h2>New posts</h2>
<ul>
{% for post in posts %}
<li>{{ post.post_title }} by <small>{{ post.post_creator }}</small></li>
<p>{{ post.post_created|timesince }}</p>
<p>Content: <span>{{ post.post_body }}</span></p>
{# <br>#}
<h3>Comments:</h3>
{% for comment in post.comments.all %}
<p>{{ comment.comment_creator }} => {{ comment.comment_body }}</p>
{% endfor %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" name="comment">
</form>
<br><br>
{% endfor %}
</ul>
{% endblock %}
Sorry for any mistakes. Thanks in advance.
I believe this error has occurred because you have tried to create a new record in the comment model that leaves the related_post field blank, when it shouldn't be. If you are happy for this field to be left blank, you can change the field to be the following in models.py :
related_post = models.ForeignKey(Post, related_name="comments", null=True)
Alternatively, you may not want this to be blank. If you add related_post to the fields in the form, a drop down box will be created with all the posts and you can select one of these to comment on.
You may also be able to automatically detect what post you are commenting on, but I'm unsure how this is done.
Thanks to #cbuch1800 I finally got the answer. Here it is and the changes to file:
In template file after {{form.as_p}} I added a line to pass the current post Primary Key(id) to view.
Template file:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="hidden" name="post_pk" value={{ post.pk }}>
<input type="submit" name="comment">
</form>
In view file retrieved the post object and added the post object to related comment.
View:
def servePage(request):
if request.method == 'POST':
form = CommentForm(request.POST)
post = Post.objects.get(pk=request.POST['post_pk'])
if form.is_valid():
comment = form.save(commit=False)
comment.related_post = post
## related_post from Comment model
comment.save()
HttpResponseRedirect(reverse('serve'))
else:
form = CommentForm()
posts = Post.objects.all()
return render(request, 'book/showpost.html', {'posts': posts, 'form': form})

Show objects properly as choices in django forms choicefield

I am working on some kind of testing application. I have two models for tests and users answers:
class TestModel(AbstractDatetimeModel):
number = models.IntegerField(unique=True)
answer_1 = models.CharField(max_length=255)
answer_2 = models.CharField(max_length=255)
class AnswerModel(AbstractDatetimeModel):
ANSWERS = (
('1', 'A'),
('2', 'B')
)
candidate = models.ForeignKey(User)
question = models.ForeignKey(TestModel)
answer = models.CharField(max_length=1, choices=ANSWERS)
And I have a form, where candidate (user) can answer each question. To implement this I use a view formset_factory:
def communicative_test(request):
questions = TestModel.objects.all()
question_formset = formset_factory(form=TestForm, extra=questions.count())
question_formset = question_formset(initial=[{'candidate': request.user, 'question': x.number,
'answer_1': x.answer_1, 'answer_2': x.answer_2} for x in questions])
return render(request, 'testing/communicative.html', locals())
On a form I need to show ChoiceField, where choices should be data from fields answer_1 and answer_2. Here is this form:
class TestForm(forms.ModelForm):
answer_1 = forms.CharField()
answer_2 = forms.CharField()
VARIANTS = (
('answer_1', answer_1),
('answer_2', answer_2)
)
variants = forms.ChoiceField(choices=VARIANTS, widget=forms.RadioSelect())
class Meta:
model = TestAnswer
fields = '__all__'
Problem is that on a page these answers displays as radio buttons with following labels:
<django.forms.fields.CharField object at 0x7f36248ef5d0>
<django.forms.fields.CharField object at 0x7f36248ef650>
I need to display it properly.
Well, as #Anentropic mentioned, I was not on the right way.
And he is right, because I need to submit only answer A or B. But for user on the form I need to display text for these answers from Test model. So I wrote two templatetags for this.
class TestForm(forms.ModelForm):
class Meta:
model = TestAnswer
fields = '__all__'
def communicative_test(request):
questions = TestModel.objects.all()
QuestionFormSet = formset_factory(form=TestForm, max_num=questions.count())
formset = QuestionFormSet(initial=[{'candidate': request.user, 'question': x.number} for x in questions])
return render(request, 'testing/communicative.html', locals())
Two custom templatetags for each answer (A/B):
#register.filter(name='get_answer1')
def get_answer1(question):
try:
question = TestModel.objects.get(number=question)
answer = question.answer_1
except ObjectDoesNotExist:
answer = ''
return answer
#register.filter(name='get_answer2')
def get_answer2(question):
try:
question = TestModel.objects.get(number=question)
answer = question.answer_2
except ObjectDoesNotExist:
answer = ''
return answer
And template is:
{% load test_tags %}
<form id="communicative-test-form" method="post">
{% csrf_token %}
{{ question_formset.management_form }}
{% for form in question_formset.forms %}
{% crispy form %}
{{ form.id }}
<p>{{ form.question.value }}</p>
<p><label>
<input type=radio name="form-{{ form.question.value|add:"-1" }}-answer" id='id_form-{{ form.question.value|add:"-1" }}-answer' value="1">
{{ form.question.value|get_answer1 }}
</label></p>
<p><label>
<input type=radio name="form-{{ form.question.value|add:"-1" }}-answer" id='id_form-{{ form.question.value|add:"-1" }}-answer' value="2">
{{ form.question.value|get_answer2 }}
</label></p>
{% endfor %}
<div style="text-align: right;" class="col-md-12">
<button type="submit">Save</button>
</div>
</form>

Django foreign key drop down

New to Django and Python and I need a little help with a foreign key drop down. Basically, I have a category model and a image model and I want users to be able to choose which category to put the image in. How do I create a drop down for the category in the image form? Are my views and html correct too? I have had a look online but I can't seem to do it myself. I keep getting errors.
Here are my models:
class Images(models.Model):
image = models.ImageField(upload_to='images', blank=False)
img_name = models.CharField(max_length=120, blank=True)
img_date = models.DateTimeField(default=now())
img_user = models.ForeignKey(User)
img_cat_id = models.ForeignKey(Categories)
def __unicode__(self):
return self.img_name
class Categories(models.Model):
cat_descr = models.CharField(max_length =120, blank=False)
def __unicode__(self):
return self.cat_descr
VIEWS:
#login_required
def upload_images(request):
context = RequestContext(request)
context_dict={}
if request.method == 'POST': # render the form, and throw it back.
# take the form data and process it!
form = UploadImagesForm(request.POST, request.FILES)
if form.is_valid():
print 'form is_valid'
upload_image = form.save(commit=False)
upload_image.img_user = request.user
if 'image' in request.FILES:
upload_image.image =request.FILES['image']
upload_image.save()
return render(request, 'rmb/upload.html', {'upload_image': form})
else:
print form.errors
# Not a HTTP POST, so we render our form using two ModelForm instances.
# These forms will be blank, ready for user input.
else:
form = UploadImagesForm()
context_dict = {'upload_image': form}
all_categories = Categories.objects.order_by('-id')
context_dict['all_categories'] = all_categories
print context_dict
return render_to_response('rmb/upload.html', context_dict, context)
FORMS:
class UploadImagesForm(forms.ModelForm):
#cat_list = ModelChoiceField(queryset=Categories.objects.all())
class Meta:
model = Images
fields=('image','img_name')
HTML:
{% block body_block %}
<form id="upload_form" method="post" action="/rmb/upload/"
enctype="multipart/form-data">
{% csrf_token %}
{{ upload_image.as_table }}
<input type="submit" name="submit" value="Upload" />
{% for categories in all_categories %}
<div> {{ categories.id }} </div>
{{ categories.cat_descr }}
<input type="submit" name="submit" value="Upload" />
{% endfor %}
</form>
{% endblock %}
You don't need to insert the HTML for the form manually, just use {{form}} in the template.
{% block body_block %}
<form id="upload_form" method="post" action="/rmb/upload/"
enctype="multipart/form-data">
{% csrf_token %}
{{ form }}
</form>
{% endblock %}
By default a ForeignKey will be a select field so you shouldn't need to do much else.
As an aside, give your models and fields more appropriate names. We know these are all image fields, because they are on the image and make sure, unless your model is a collection of things, you give it a singular name. Lastly, when using a Foreign Key and item gets an extra field of fieldname_id that is just the ID, whereas fieldname is the property that gives the related item as well.
So instead of:
class Images(models.Model):
image = models.ImageField(upload_to='images', blank=False)
img_name = models.CharField(max_length=120, blank=True)
img_date = models.DateTimeField(default=now())
img_user = models.ForeignKey(User)
img_cat_id = models.ForeignKey(Categories)
Use:
class Image(models.Model):
image = models.ImageField(upload_to='images', blank=False)
name = models.CharField(max_length=120, blank=True)
date = models.DateTimeField(default=now())
user = models.ForeignKey(User)
category = models.ForeignKey(Categories)