I'm following a tutorial on effectivedjango.com, and this is the code they have:
views.py:
class CreateContactView(CreateView):
model = Contact
template_name = 'edit_contact.html'
fields = '__all__' #this is needed for error msg Using ModelFormMixin (base class of CreateContactView) without the 'fields' attribute is prohibited.
def get_success_url(self):
return reverse('contacts-list')
def get_context_data(self, **kwargs):
context = super(CreateContactView, self).get_context_data(**kwargs)
context['action'] = reverse('contacts-new')
return context
class UpdateContactView(UpdateView):
model = Contact
template_name = 'edit_contact.html'
fields = '__all__'
def get_success_url(self):
return reverse('contacts-list')
def get_context_data(self, **kwargs):
context = super(UpdateContactView, self).get_context_data(**kwargs)
context['action'] = reverse('contacts-edit', kwargs={'pk' : self.get_object().id})
return context
urls.py:
url(r'^$', contacts.views.ListContactView.as_view(),
name='contacts-list',),
url(r'^new$', contacts.views.CreateContactView.as_view(),
name='contacts-new',),
url(r'^edit/(?P<pk>\d+)/$', contacts.views.UpdateContactView.as_view(),
name='contacts-edit',),
contact_list.html:
{% block content %}
<h1>Contacts</h1>
<ul>
{% for contact in object_list %}
<li class="contact">
{{ contact }}
(edit)
</li>
{% endfor %}
</ul>
Add contact
{% endblock %}
edit_contact.html:
{% block content %}
{% if contact.id %}
<h1>Edit Contact</h1>
{% else %}
<h1>Add Contact</h1>
{% endif %}
<form action="{{ action }}" method="POST">
{% csrf_token %}
<ul>
{{ form.as_ul }}
</ul>
<input id="save_contact" type="submit" value="Save" />
</form>
Back to list
{% if contact.id %}
Delete
{% endif %}
{% endblock %}
Why does the line context['action'] = reverse('contacts-edit', kwargs={'pk' : self.get_object().id}) in views.py look like its calling itself?
What I mean is, this action is called when the submit button is pressed in the contact-edit template, correct? So it starts there, and it is reverse-calling contact-edit which is itself, right?
What am I not seeing here?
Thank you for all your help.
Yes, the line context['action'] = reverse('contacts-edit', kwargs={'pk' : self.get_object().id}) in views.py is calling itself. This line generates the proper url for contacts-edit view.
This is done so that POST requests come to the same view i.e. UpdateContactView which is an UpdateView. There, proper handling will be done i.e. form validation will occur with the sent data. If the form is valid, object will be updated. Otherwise, the form will be displayed again with errors.
Django docs on UpdateView:
A view that displays a form for editing an existing object,
redisplaying the form with validation errors (if there are any) and
saving changes to the object.
Related
I am confronted for the first time to a situation where I want the user to select model column values based on some constraint. The goal is to have the user select something and then output the result below the form on the same page.
I am not sure what I am doing wrong but submiting the form output:
Select a valid choice. That choice is not one of the available choices.
Here is what I have been able to do:
forms.py
class SelectBuildingForm(forms.Form):
filename = forms.ModelChoiceField(queryset=Building.objects.none())
def __init__(self, *args, **kwargs):
us = args[1] or None
forms.Form.__init__(self, *args, **kwargs)
self.fields['filename'].queryset = Building.objects.filter(project=us).values_list('filename',flat=True)
views.py
#login_required
#owner_required
def FileSelectionView(request,pk):
form = SelectBuildingForm(request.POST or None, pk)
# if form.is_valid():
# filename = form.cleaned_data('filename')
# print(filename)
# return redirect('comparator_test')
return render(request,'files_selection.html', {'form':form})
and template
<div class="mt-5 md:col-span-2 md:mt-0">
<form method="POST" id="my-form">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" value="Select">GOOOO</button>
</form>
{% if form.is_valid %}
{% for choice in form.cleaned_data.choices %}
<p>{{ choice }}</p>
{% endfor %}
{% endif %}
</div>
I have tried a couple options to validate the selection but none of them work. Can a pair of fresh eyes see where I am messing up?
I am trying to create a way to edit individual blog posts from their individual html. Here are the relevant files and trace back. I am somewhat understanding that the issue lies in blog_post.id being due to the fact that blog_post has not carried over from the for loop on blog_posts.html. I have read up on others having this issue and they all structured their pages to have the edit button being inside the original for loop, which makes sense in hindsight. BUT now that I have run into this issue, I'm determined to understand how I can solve it without going back and restructuring my pages to align with the others I saw.
urls.py
from django.urls import path
from . import views
app_name = 'blogs'
urlpatterns = [
# Home page
path('', views.index, name='index'),
path('blog_posts/', views.blog_posts, name='blog_posts'),
path('blog_posts/<int:blog_post_id>/', views.blog_post, name='blog_post'),
path('new_blog_post/', views.new_blog_post, name='new_blog_post'),
path('edit_blog_post/<int:blog_post_id>/', views.edit_blog_post, name='edit_blog_post'),
]
views.py
from .models import BlogPost
from .forms import BlogPostForm
def index(request):
"""Home page for Blog."""
return render(request, 'blogs/index.html')
def blog_posts(request):
"""Show all Blog Posts."""
blog_posts = BlogPost.objects.order_by('date_added')
context = {'blog_posts': blog_posts}
return render(request, 'blogs/blog_posts.html', context)
def blog_post(request, blog_post_id):
"""Show details of an individual blog post."""
blog_post = BlogPost.objects.get(id=blog_post_id)
title = blog_post.title
id = blog_post_id
date = blog_post.date_added
text = blog_post.text
context = {'title': title, 'text': text, 'date': date}
return render(request, 'blogs/blog_post.html', context)
def new_blog_post(request):
"""Add a new blog post"""
if request.method != 'POST':
# No data submitted, create a blank form.
form = BlogPostForm()
else:
# POST data submitted, process data.
form = BlogPostForm(data=request.POST)
if form.is_valid():
form.save()
return redirect('blogs:blog_posts')
# Display a blank or invalid form.
context = {'form': form}
return render(request, 'blogs/new_blog_post.html', context)
def edit_blog_post(request, blog_post_id):
"""Edit an existing blog post's title or text."""
blog_post = BlogPost.objects.get(id=blog_post_id)
if request.method != 'POST':
# Initial request, prefill with the current data.
form = BlogPostForm(instance=blog_post)
else:
# POST data submitted; process new data.
form = BlogPostForm(instance=blog_post, data=request.POST)
if form.is_valid():
form.save()
return redirect('blogs:blog_post', blog_post_id=blog_post.id)
context = {'blog_post': blog_post, 'form': form}
return render(request, 'blogs/edit_blog_post.html', context)
models.py
from django.db import models
class BlogPost(models.Model):
"""A post the user is posting on their blog."""
title = models.CharField(max_length=200)
text = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
"""Return a string representation of the model"""
return f"{self.title.title()}"
blog_posts.html
{% extends 'blogs/base.html' %}
{% block content %}
<p>Blog Posts</p>
<ul>
{% for blog_post in blog_posts %}
<li>
{{ blog_post }}
</li>
{% empty %}
<li>No posts have been made yet.</li>
{% endfor %}
</ul>
Add a new blog post
{% endblock content %}
blog_post.html
{% extends 'blogs/base.html' %}
{% block content %}
<p>Blog Post: {{ title }}</p>
<p>Entry:</p>
<p>{{ text }}</p>
<p>{{ date }}</p>
<p>
Edit Blog Post
</p>
{% endblock content %}
edit_blog_post.html
{% extends "blogs/base.html" %}
{% block content %}
<p>
{{ blog_post }}
</p>
<p>Edit Blog Post</p>
<form action="{% url 'blogs:edit_blog_post' blog_post.id %}" method='post'>
{% csrf_token %}
{{ form.as_p }}
<button name="submit">Save Changes</button>
</form>
{% endblock content %}
Reverse for 'edit_blog_post' with arguments '('',)' not found. 1 pattern(s) tried: ['edit_blog_post/(?P<blog_post_id>[0-9]+)/\Z']
3 {% block content %}
4
5 <p>Blog Post: {{ title }}</p>
6
7 <p>Entry:</p>
8
9 <p>{{ text }}</p>
10 <p>{{ date }}</p>
11
12 <p>
13 Edit Blog Post
14 </p>
15
16 {% endblock content %}
If I've read the question correctly, You're getting the error becuase you are not providing the necessary ID to the URL construction part of your template.
You're separating out the elements (date, content etc) to send to the template, but not passing the ID at the same time. You could send the ID in as a separate context variable, but that's extra typing for no real reward.
It's easiest to pass in the post itself via context and refer to its attributes in the template - I think it makes it easier to read also. That way the ID is there when you need to contruct the edit link, and if you change the model to possess extra fields, you don't need to convert and add to the context as the whole post is already there.
views.py
def blog_post(request, blog_post_id):
"""Show details of an individual blog post."""
blog_post = BlogPost.objects.get(id=blog_post_id) #this is all we need
context = {"blog_post_context": blog_post}
return render(request, 'blogs/blog_post.html', context)
blog_post.html
{% extends 'blogs/base.html' %}
{% block content %}
<p>Blog Post: {{ blog_post_context.title }}</p>
<p>Entry:</p>
<p>{{ blog_post_context.text }}</p>
<p>{{ blog_post_context.date }}</p>
<p>
Edit Blog Post
</p>
{% endblock content %}
If that all works, look into using get_object_or_404 rather than Post.objects.get for some additional robustness.
I assume you got the error when you try visiting the blog_post.html page. If I'm correct, then here's an approach you could take...
In your views.py
def blog_post(request, blog_post_id):
"""Show details of an individual blog post."""
# blog_post = BlogPost.objects.get(id=blog_post_id)
blog_post = get_object_or_404(id=blog_post_id) # Recommended
# Commented lines below are somewhat not necessary...
# title = blog_post.title
# id = blog_post_id
# date = blog_post.date_added
# text = blog_post.text
context = {'blog_post': blog_post}
return render(request, 'blogs/blog_post.html', context)
edit_blog_post.html is expecting an object called blog_post to be able to access the blog_post.id for {% url 'blogs:edit_blog_post' blog_post.id %}.
Now within the edit_blog_post.html file.
{% block content %}
<p>Blog Post: {{ blog_post.title }}</p>
<p>Entry:</p>
<p>{{ blog_post.text }}</p>
<p>{{ blog_post.date_added }}</p>
<p>
Edit Blog Post
</p>
{% endblock content %}
I'm using this app to register the user of my website https://github.com/egorsmkv/simple-django-login-and-register. The problem is that no metter what I do my form are not visible (they were when I did not use this registration app and the code worked just fine). This is my code:
model
class UserBio(models.Model):
name = models.CharField(max_length=120)
age = models.CharField(max_length=2)
phone = models.CharField(max_length=10)
height = models.CharField(max_length=3)
weight = models.CharField(max_length=3)
form
class UserBio(forms.ModelForm):
class Meta:
model = UserBio
fields = (name', 'age', 'phone', 'height', 'weight')
views
def add_bio(request):
submitted = False
if request.method == "POST":
info_form = UserBio(request.POST)
if info_form.is_valid():
info_form.save()
return HttpResponseRedirect('add_information?submitted=True')
else:
info_form = UserBio()
if 'submitted' in request.GET:
submitted = True
return render(request, 'accounts/profile/add_information.html', {'form': info_form, 'submitted':submitted})
urls
urlpatterns = [
path('add/information', views.add_information, name='add_information'),
]
html
{% extends 'layouts/default/base.html' %}
{% block title %} Add info {% endblock %}
{% load i18n %}
{% block content %}
<h4>{% trans 'Add Info' %}</h4>
{% if submitted %}
Sumitted correctly
{% else %}
<form method="post">
{% csrf_token %}
{{ info_form.as_p }}
</form>
</div>
<br/>
</body>
{% endif %}
{% endblock %}
Any help would be very apprecieted!
because in your views def add_bio change your url acc to your function views
path('add/information', views.add_bio, name='add_information'),
and in you template
{{ form.as_p }}
You passed the info_form variable to the template with variable name form. Indeed:
# name of the variable for the template ↓
return render(request, 'accounts/profile/add_information.html', {'form': info_form, 'submitted':submitted})
This thus means that you render this with:
{{ form.as_p }}
You should also trigger the correct view:
urlpatterns = [
path('add/information/', views.add_bio, name='add_information'),
]
The path does not point to the template: a path points to a view, and the view can (this is not required) render zero, one or multiple templates to create a HTTP response.
Disclaimer: I am aware of several other posts that raise my issue, but they do not apply to my code.
Hi everyone, I am creating a knockoff of stackoverflow using Django, and I came into an issue.
I have a View that shows a question that has been asked. Obviously, anyone coming to that question should be able to answer it. Here are the views and templates:
This view displays the question and some other info:
class QuestionsDetailView(DetailView):
model = Question
template_name = "function/questions/questions_detail.html"
def get_context_data(self, *args, **kwargs):
context = {}
question_detail = Question.objects.filter(id=self.kwargs.get("pk")).first()
answers = Answer.objects.filter(question=question_detail)
context["question"] = question_detail
context["questioncomments"] = QuestionComment.objects.filter(question=question_detail)
context["answers"] = [[answer, AnswerComment.objects.filter(answer=answer)] for answer in answers]
return context
This view allows you to answer:
class AnswersCreateView(LoginRequiredMixin, CreateView):
model = Answer
fields = ["content"]
template_name = "function/answers/answers_create.html"
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def get_success_url(self):
return reverse("function-questions-detail", kwargs={"pk": self.kwargs.get("pk")})
On stackoverflow, when you view a question, you can answer it by scrolling down and providing your answer. I wanted to do something similar, so instead of having the answer view on a separate page, I put in in the question detail page using the 'include' template tag. Here are the templates:
This is the template for the question (which contains the include statement (bottom)):
{% extends 'core/base.html' %}
{% block content %}
<!--Question-->
<h1>{{ question.title }} by {{ question.author }}</h1>
{% if user == question.author %}
Update
Delete
{% endif %}
<h3>{{ question.content }}</h3>
<!--QuestionComments-->
{% for comment in questioncomments %}
<h6>{{ comment }} -- {{ comment.author }}</h6>
{% endfor %}
<!--Answers-->
{% if answers %}
{% for pair in answers %}
<!--Answers-->
<h3>{{ pair.0.content }}</h3>
<h5>{{ pair.0.author }}</h5>
<!--AnswerComments-->
{% for comment in pair.1 %}
<h6>{{ comment }}</h6>
{% endfor %}
{% endfor %}
{% endif %}
{% include 'function/answers/answers_create.html' %}
{% endblock content %}
Here is the template for the form which allows you to create an answer (this is the template that is referenced by the include template tag):
{% load crispy_forms_tags %}
<form method="POST">
{% csrf_token %}
<h2>Your Answer</h2>
{{ form|crispy }}
<button type="submit">Submit</button>
</form>
When I tried it out on the website in a browser, the question was rendering but the answer form was rendering everything except the form fields. It is rendering 'Your Answer' and the submit button but it is not rendering the actual field.
When I press submit I get the following error in my command prompt:
Method Not Allowed (POST): /questions/3/
Method Not Allowed: /questions/3/
So, in actual fact, the problem is how do I render the form properly. And will that solve the issue?
Thanks in advance.
Use form and pass it through context in your DetailView.
#form
class AnswerCreateForm(forms.ModelForm):
class Meta:
fields = ['content']
#views
class QuestionsDetailView(DetailView):
.......
def get_context_data(self, *args, **kwargs):
......
context['form'] = AnswerCreateForm()
return context
# create view
# provide same template name as the detail view.
class AnswersCreateView(LoginRequiredMixin, CreateView):
model = Answer
form_class = AnswerCreateForm
template_name = "function/questions/question_detail.html"
I am trying to add the profile image of each user(designer) in the below list view
For each designer, there is a profile image that has already been uploaded before I am just trying to get it and show it in the UserPost List View.
Currently, with the below code, the designer image is not showing.
Here is the views.py
class UserPostListView(ListView):
model = Post
template_name = "user_posts.html"
context_object_name = 'posts'
queryset = Post.objects.filter(admin_approved=True)
paginate_by = 6
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(designer=user, admin_approved=True).order_by('-date_posted')
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
has_items = Item.objects.filter(designer__username=self.kwargs['username']).exists()
context['has_items'] = has_items
return context
Here is the template
{% if has_items %}
<h1> Hello, this is {{ view.kwargs.username }} </h1>
--------------------------------------
<img class="profile_image" src={{ designer.profile.image.url }}> <----------- I want it to appear
{% else %}
<h1>Hello, this is {{ view.kwargs.username }} </h1>
--------------------------------------
<img class="profile_image" src={{ designer.profile.image.url }}> <----------- I want it to appear
{% endif %}
Problem
The issue seems to be that your template is missing a for loop to loop through posts so that you can access the respective designer. Also, I'm assuming that view is accessible from a post object as it's not explicitly defined anywhere else in your code example. Lastly, your else loop is doing the same as the if loop.
Solution
Include a for loop in template so that you can access designers' profile image URLs.
{% if has_items %}
{% for post in posts %}
<h1> Hello, this is {{ post.view.kwargs.username }} </h1>
--------------------------------------
<img class="profile_image" src={{ post.designer.profile.image.url }}>
{% endfor %}
{% else %}
...
{% endif %}
References
ListView Documentation https://docs.djangoproject.com/en/3.1/ref/class-based-views/generic-display/#listview
ListView Code https://github.com/django/django/blob/master/django/views/generic/list.py#L194