I'm trying to clone the Instagram web page using Django(version-3.1).
My Django project has an app called 'post'.
One of its template I have a form which is posting a comment to a post. The form post request should call the path('add_comment/',views.add_comment,name='add_comment'), but It's calling path('<slug:slug>/',views.post_details,name='post_details'), instead. And raising DoesNotExist at /post/add_comment error. I added print() statement at the beginning of both add_comment() and post_details() methods to find out which is running when the request is made. I have no idea what I have done wrong.
The project GitHub link - https://github.com/mirasel/Instagram_Clone
the post_details.html template is -
{% extends 'base.html' %}
{% load static %}
{% block title %} post {% endblock %}
{% block profilephoto %} {{ propic.url }} {% endblock %}
{% block body %}
<div>
<div>
<img src="{{post.image.url}}" alt="post" height="250px" width="250px">
</div>
<div>
<a href="{% url 'instagram:profile' post.uploader %}">
<img src="{{uploader.profile_pic.url}}" alt="{{uploader}}" style="border-radius: 50%;" height="24px" width="24px">
{{ post.uploader }}
</a><br>
<p>{{ post.date_published.date }}</p>
</div>
<div>
<p>{{ post.caption }}</p>
</div>
<div>
<form action="{% url 'post:add_comment' %}" id="comment_form" method="POST">
{% csrf_token %}
<textarea name="comment" id="comment" cols="30" rows="1" placeholder="Write a comment..."></textarea>
<input type="hidden" name="slug" id="slug" value="{{post.slug}}">
<!-- <input type="submit" style="display: none;" name="submit"> -->
</form>
<script>
$(function(){
$("#comment").keypress(function (e) {
if(e.which == 13 && !e.shiftKey) {
$(this).closest("form").submit();
e.preventDefault();
}
});
});
</script>
{% endblock %}
the views.py -
from django.shortcuts import render,redirect
from instagram.views import get_nav_propic,get_profile_details
from .models import UserPost,PostComment,PostLike
from django.http import JsonResponse
def get_post_likes(post):
likes = PostLike.objects.filter(post=post)
total_likes = len(likes)
likers = []
for l in likes:
likers.append(get_profile_details(l.liker))
return {'likers':likers,'total_likes':total_likes}
def get_post_comments(post):
comments = PostComment.objects.filter(post=post)
total_comments = len(comments)
commenter = []
comment = []
for c in comments:
commenter.append(get_profile_details(c.commenter))
comment.append(c.comment)
postcomment = zip(commenter,comment)
return {'post_comment':postcomment,'total_comments':total_comments}
def upload_post(request):
if request.method == 'POST':
image = request.FILES['post_img']
caption = request.POST['caption']
uploader = request.user
UserPost.objects.create(uploader=uploader,image=image,caption=caption)
return redirect('instagram:feed')
else:
context = {
'propic' : get_nav_propic(request.user)
}
return render(request,'post/upload_post.html',context)
def post_details(request,slug):
print('I am here in post details')
post = UserPost.objects.get(slug=slug)
context = {
'propic' : get_nav_propic(request.user),
'post' : post,
'uploader' : get_profile_details(post.uploader),
'LIKES' : get_post_likes(post),
'COMMENTS' : get_post_comments(post),
}
return render(request,'post/post_details.html',context)
def add_comment(request):
print('I am here in add comment')
if request.method == 'POST':
post_slug = request.POST.get('slug')
post = UserPost.objects.get(slug=post_slug)
user = request.user
comment = request.POST.get('comment')
PostComment.objects.create(post=post,commenter=user,comment=comment)
return redirect('post:post_details',slug=post_slug)
the urls.py -
from django.urls import path
from . import views
app_name='post'
urlpatterns = [
path('upload_post/',views.upload_post,name='upload_post'),
path('<slug:slug>/',views.post_details,name='post_details'),
path('add_comment/',views.add_comment,name='add_comment'),
]
The error - Error page
Solved
I had to make the URL path of add_comment as following-
#previous one
path('add_comment/',views.add_comment,name='add_comment'),
#modified one
path('comment/add_comment/',views.add_comment,name='add_comment'),
This is because the pattern for the slug URL and add comment URL were similar.
Because Django will process the urlpatterns sequentially, from docs:
Django runs through each URL pattern, in order, and stops at the first
one that matches the requested URL, matching against path_info.
And '/add_comment' is a valid slug <slug:slug>, so post_details will be called.
So you should keep the definition of the most generic url patterns at last:
urlpatterns = [
path('upload_post/',views.upload_post,name='upload_post'),
path('add_comment/',views.add_comment,name='add_comment'),
path('<slug:slug>/',views.post_details,name='post_details'),
]
Hopefully this will work for you.
Related
I'm trying to add data that I rendered on a page from an API endpoint, to my database when I click "Add to my records" button, as can be seen in the image below, and I'm only trying to store "Date and Country" into the database (my model table has only date and country)
enter image description here
I've seen many resources talking about how JS and AJAX are useful in this case but I'm lost with logic of it all. Is there any way someone could explain how it's supposed to be done.
models.py
from django.db import models
class CountryData(models.Model):
country = models.CharField(max_length=100)
date = models.DateTimeField()
def __str__(self):
return self.country
views.py
def all_countries(request):
first_response = requests.get("https://api.covid19api.com/summary").json()
results = len(first_response["Countries"])
my_new_list = []
data_list = []
for i in range(0, results):
my_new_list.append(first_response["Countries"][i])
# print(my_new_list)
if request.method == "POST":
if request.POST.get("country") and request.POST.get("date"):
added_record = CountryData()
added_record.country = request.POST.get("country")
# 2022-12-19T08:53:48.179Z
added_record.date = datetime.datetime.strptime(
request.POST.get("date"), "%Y-%m-%dT%I:%M:%S.%fZ"
)
added_record.save()
return render(request, "allcountries.html")
else:
return render(request, "allcountries.html", )
context = {"my_new_list": my_new_list}
return render(request, "allcountries.html", context)
urls.py
from django.urls import path, include
from .views import home, all_countries
urlpatterns = [
path("", home, name="home"),
path("allcountries", all_countries, name="all_countries")
]
allcountries.html
{% extends '_base.html' %}
{% block page_title %} Covid19 Statistics For All Countries {% endblock %}
{% block content %}
<h3 class="text-center">Covid19 Statistics For All Countries </h3>
{% for object in my_new_list %}
<div class="row justify-content-center">
<div class="col-sm-10 d-flex justify-content-center">
<div class="card text-dark text-center" style="width: 20rem;">
<div class="card-block card-body">
<form method="POST" action="">
{% csrf_token %}
<h5 class="card-header" name="country">Country: {{object.Country}}, {{object.CountryCode}}</h5>
<br>
<p class="card-text">Total Confirmed Cases: {{object.TotalConfirmed}} </p>
<p class="card-text">Total Deaths Cases: {{object.TotalDeaths}} </p>
<p class="card-text">Total Recovered Cases: {{object.TotalRecovered}} </p>
<p class="card-text" name="date">Date: {{object.Date}}</p>
<button class="btn btn-success" type="submit">ADD TO MY RECORDS </button>
</form>
</div>
</div>
<br>
</div>
</div>
{% endfor %}
{% endblock %}
You can use the following ajax snippet to send data in the backend:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script>
$.ajax({
type: 'POST',
url: 'url', #leave blank if URL is same as the current one
data: {
'country': 'county_name',
'date': 'date_value',
},
success: function(response) {
# depending upon the response from the server you can make an alert here.
},
error: function(response){
# If some error occurred on the backend you can show error message from here
}
});
</script>
I'm making a messaging system which has a message model and some views for things like inbox and conversation.
Inbox view and template works and is linked to the conversation through the id of the sender. However, when I click on the senders name (The senders name is the link (template link is {{ sender }}) that is meant to trigger the conversation view which is supposed to render the template convo.html ( the project level url for that is url(r'^messaging/inbox/conversation/(?P<id>[\w-]+)/$', message_views.Conversation.as_view(), name="conversation"),) ), Django changes the url but stays on the same page(So it triggers the inbox view and stays on inbox.html and changes the url but doesn't trigger the conversation view and doesn't render the convo.html template). I've tried changing from a function based view to a class based view and it did nothing. What am I doing wrong?
Note: I'm still new to django so forgive me for any bad practices; I'm attempting to teach myself.
views.py (inbox and conversation views)
def inbox(request):
messages = Message.objects.filter(receiver=request.user.id)
senders = {}
for message in messages:
if message.sender not in senders:
senders[message.sender] = message
return render(request, 'inbox.html', {'senders': senders})
# There is only ever one of this view, either the CBV or FBV
def conversation(request, other_user_id):
print(request.method)
if request.method == "POST":
reply = request.post['reply']
other_user = CustomUser.objects.get(id=other_user_id)
message = Message(sender=request.user, receiver=other_user)
message.save()
return redirect("conversation")
else:
other_user = CustomUser.objects.get(id=other_user_id)
messages = Message.objects.filter(Q(receiver=request.user) & Q(sender=other_user))
print(messages)
return render(request, 'convo.html', {'messages': messages})
# Class based conversation view
class Conversation(View):
def post(request, other_user_id):
reply = request.post['reply']
other_user = CustomUser.objects.get(id=other_user_id)
message = Message(sender=request.user, receiver=other_user)
message.save()
return redirect("conversation")
def get(request, other_user_id):
other_user = CustomUser.objects.get(id=other_user_id)
messages = Message.objects.filter(Q(receiver=request.user) & Q(sender=other_user))
print(messages)
return render(request, 'convo.html', {'messages': messages})
template - inbox.html
<!DOCTYPE html>
{% extends "accbase.html" %}
<!--Omitted outbox in favor of conversation-->
{% block content %}
<div class="inbox">
<h1>Inbox</h1>
<hr>
{% for sender in senders %}
{{ sender }}
<br>
{{sender.message.created_at}}
<br>
{% endfor %}
</div>
{% endblock %}
template - convo.html
<!DOCTYPE html>
<!--Template for reciever side of conversation-->
{% extends "accbase.html"%}
{% block content %}
<div class="conversation">
<h1>Conversation with {{sender}}</h1>
<p>
{% for msg in messages %}
<!--Order is descending ~~ bottom to top: newest to oldest-->
{{ msg.content }}
<br>
{% endfor %}
<hr>
<form method="POST">
<label>Reply</label>
<input type="text" name="reply">
</form>
</p>
<button type="submit">Send</button>
</div>
{% endblock %}
project level urls.py
from django.urls import path, re_path, include
from django.conf.urls import url
from messaging import views as message_views
urlpatterns = [
url(r'^messaging/inbox', message_views.inbox, name="inbox"),
url(r'^messaging/inbox/conversation/(?P<id>[\w-]+)/$', message_views.Conversation.as_view(), name="conversation"),
]
As Daniel Roseman said in comments " The problem here is that you didn't terminate the regex for your inbox URL, so it also matches the conversation URL. " so I added the needed $ to the end of the inbox url pattern and that fixed it!
urls.py:
urlpatterns = [
path('employee/add_employee/', views.add_employee, name='add-employee'),
path('employee/add_employee/add/', views.add_employee_action, name='add-employee-action'),
]
I have add-employee page and some forms to fill there.
views.py:
def add_employee(request):
personal_form = PersonalEmployeeForm()
history_form = EmployeeHistoryForm()
return render(
request,
'sections/add_employee.html',
context={
'personal_form': personal_form,
'history_form': history_form,
}
)
def add_employee_action(request):
if request.method == "POST":
personal_form = PersonalEmployeeForm(request.POST)
history_form = EmployeeHistoryForm(request.POST)
if personal_form.is_valid() and history_form.is_valid():
# here is some logic with models
return redirect('add-employee')
else:
personal_form = PersonalEmployeeForm()
history_form = EmployeeHistoryForm()
return render(
request,
'sections/add_employee.html',
context={
'personal_form': personal_form,
'history_form': history_form,
}
)
template:
<form id="a-submit-form" action="add/" method="POST">
{% csrf_token %}
<div class="column-wrapper">
<div class="column">
<div class="form-wrapper">
{% for field in personal_form.visible_fields %}
{% include "elements/forms/form_line.html" %}
<br>
{% endfor %}
</div>
</div>
<div class="column">
<div class="form-wrapper">
{% for field in history_form.visible_fields %}
{% include "elements/forms/form_line.html" %}
<br>
{% endfor %}
</div>
</div>
</div>
<div class="button-bar-wrapper">
<div class="button_bar">
<a class="a-button positive" id="submit">Добавить</a>
<a class="a-button" href="{% url 'employee' %}">Сотрудники</a>
<a class="a-button" href="{% url 'index' %}">На главуную</a>
</div>
</div>
</form>
Submitting by <a> element is tested and worked well with jQuery script.
The problem is after submitting invalid forms I have a page with blah-blah/employee/add_employee/add/ URL. And if I try to submit forms again I have a page with blah-blah/employee/add_employee/add/add/ URL, which is incorrect. How can I render the page with blah-blah/employee/add_employee/ URL and show all error messages?
This is likely because you have written a relative URL in the <form> tag of the sections/add_employee.html template. The template thus contains something like:
<form method="post" action="add/">
...
</form>
You can use a URL with the {% url … %} template tag [Django-doc]:
<form method="post" action="{% url 'add-employee-action' %}">
...
</form>
Furthermore one usually uses the same path to handle both the GET and the POST request. So in fact you might simply remove the 'add-employee' path.
I have no Idea why, but if I try to 'GET' this view I get this error:
django.urls.exceptions.NoReverseMatch: Reverse for 'profile-update-name' with arguments '('',)' not found. 1 pattern(s) tried: ['ce_profiles/profile\\-update\\-name/(?P<pk>[0-9]+)/$']
But if submit a 'POST' request, everything is fine. ???
Here is the work flow:
First I access this view and template:
def customer_profile(request, pk):
name = get_object_or_404(CEName, id=pk)
return render(
request,
'customer/customer_profile.html',
{'profile_name': name}
)
{% block content %}
{% include 'ce_profiles/name_header.html' %}
<div id="name" class="container d-flex justify-content-between pt-1">
{% include 'ce_profiles/profile_name.html' %} <!-- displays the profile_name -->
<button id="update_button" action="{% url 'ce_profiles:profile-update-name' profile_name.id %}" class="bold btn btn-main btn-sm button-main">UPDATE</button>
</div>
<div id="div_NameForm" class="container">
</div>
{% endblock %}
{% block script %}
<script src="{% static 'ce_profiles/ce_profiles.js' %}"></script>
{% endblock %}
When I click the update button it should AJAX in the form to be modified. I've tried substitution of the variable to just a '1', but it still doesn't work. I have tests to verify that the view by itself doesn't work with the 'GET', but does with the 'POST', apart from the previous view.
urls.py
app_name='ce_profiles'
urlpatterns = [
path('profile-update-name/<int:pk>/', views.profile_update_name,
name='profile-update-name'
),
]
view
def profile_update_name(request, pk):
name = get_object_or_404(CEName, id=pk)
if request.POST:
name_form = NameForm(data=request.POST, instance=name)
if name_form.has_changed() == False:
name_form.add_error(None, _('No changes made.'))
if name_form.is_valid():
name_form.save()
updated_name = get_object_or_404(CEName, id=name.id)
profile_name_json = render_to_string(
'ce_profiles/profile_name.html',
{'profile_name': updated_name,}
)
return JsonResponse({'profile_name': profile_name_json})
else:
return JsonResponse({'error': name_form.errors})
else:
name_form = NameForm(instance=name)
get_NameForm_json = render_to_string(
'ce_profiles/update_NameForm.html',
{'NameForm': name_form}
)
return JsonResponse({'NameForm': get_NameForm_json})
Here is the template. Is something missing?
update_NameForm.html
<div id="div_NameForm" class="container">
<hr size="3px">
<form id="NameForm" method="POST" action="{% url 'ce_profiles:profile-update-name' profile_name.id %}">
{% csrf_token %}
{{ NameForm.as_p }}
<div id="NameForm_errors"></div>
<br>
<button id="save_changes" type="submit" class="btn btn-main button-main btn-block">Save Changes</button>
</form>
</div>
Instead of using render_to_string, you may use render(), and you forgot to send the variable profile_name: to the templates.
def profile_update_name(request, pk):
name = get_object_or_404(CEName, id=pk)
if request.POST:
''' code '''
# no need to use else statement
# since 'return' breaks the 'if request.POST' statement
name_form = NameForm(instance=name)
context = {
profile_name:name,
'NameForm': name_form,
}
return render(request,'ce_profiles/update_NameForm.html',context)
Note that you can you leave action='' empty in the form which will mean the same view
<form id="NameForm" method="POST" action="">
I have a ModelForm that posts news items to a database, and it uses a javascript textarea to allow the authorized poster to insert certain pieces of HTML to style text, like bold and italics. However, since I have the template output using the "safe" filter, it outputs all the HTML the form widget tries to pass on. This includes a bothersome <br> tag that never goes away, making it so you can submit without form validation reading the field as empty and stopping you. How can I make that I can not only filter the <br> tag, but completely remove it from the data? Here is relevant code:
Models.py:
from django.db import models
from django.forms import ModelForm, forms
from django.contrib.auth.models import User
# Create your models here.
class NewsItem(models.Model):
user = models.ForeignKey(User)
date = models.DateField(auto_now=True)
news = models.TextField(max_length=100000, blank=False, help_text='HELP TEXT')
def __unicode__(self):
return u'%s %s %s' % (self.user, self.date, self.news)
class NewsForm(ModelForm):
class Meta:
model = NewsItem
exclude=('user','date',)
Views.py:
from news.models import NewsForm, NewsItem
from django.shortcuts import render
from django.http import HttpResponseRedirect, HttpResponse
def news(request):
if request.method == 'POST':
item = NewsItem(user=request.user)
form = NewsForm(request.POST, instance=item)
if form.is_valid():
form.save()
return HttpResponseRedirect('/news/')
else:
form = NewsForm()
news_list = NewsItem.objects.all()
return render(request, 'news_list.html', {'news_list': news_list, 'form': form})
news_list.html:
{% extends "base.html" %}
{% block title %}News in the Corps{% endblock %}
{% block content %}
<h2 id="page_h">News in the Corps</h2>
{% if user.is_authenticated %}
<h3>Post News</h3>
<script src="{{ STATIC_URL }}nicEdit.js" type="text/javascript"></script>
<script type="text/javascript">bkLib.onDomLoaded(nicEditors.allTextAreas);</script>
<div id="news_poster">
<form id="news_poster" action="/news/" method="POST">{% csrf_token %}
{{ form }}
<input type="submit" value="Submit" />
</form>
</div>
{% endif %}
<ul id="events_list">
{% if news_list %}
<div id="news_list">
{% for news in news_list %}
{% if news.id == 1 %}
<hr />
{% endif %}
<div id="{{ news.id }}" class="news_item">
<p class="poster">Posted By: {{ news.user }} | Posted On: {{ news.date }} | Link</p>
<div id="news_item">
{{ news.news|safe }}
</div>
</div>
<hr />
{% endfor %}
</div>
{% endif %}
</ul>
{% endblock %}
You can try the removetags template filter:
{{ news.news|removetags:"br"|safe }}
I can't help but thinking that the "removetags" as Timmy O'Mahony suggested might work if it was structured like this:
{{ news.news|safe|removetags:"br"}}
Give it a shot and see if it works. I would reply, but my karma's not height enough to directly reply to an answer with a suggestion.