I'm using Django modelform and it's submitted by pressing the enter key. This is what I wanted, but I don't know why it works. I didn't add any JS codes related to keydown but other codes to practice Ajax. Also, I found out that when there's only one input inside the form, it's submitted with the enter key, but my form has two inputs.
What I'm doing is to add a comment on a post like instagram. I used Ajax to create a comment instance.
models.py
class Comment(models.Model):
parent_post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="comments")
author = models.CharField(max_length=10)
content = models.CharField(max_length=100)
forms.py
class CommentForm(ModelForm):
class Meta:
model = Comment
exclude = ["parent_post"]
HTML
{% for post in posts %}
<form method="POST" data-id="{{post.id}}" class="d-flex align-items-center w-100 mt-2">
{% load widget_tweaks %} <!-- this is a django package for easy CSS -->
{% csrf_token %}
{{ form.non_field_errors }}
<div class="fieldWrapper">
{{ form.author.errors }}
{{ form.author|add_class:"form__author"|attr:"placeholder:name" }}
</div>
<div class="fieldWrapper w-100">
{{ form.content.errors }}
{{ form.content|add_class:"form__content"|attr:"placeholder:content" }}
</div>
<button class="btn btn-sm btn-warning w-auto">Write</button>
</form>
{% endfor %}
JS (Here I didn't put the codes after getting JSON response from views.py)
const forms = document.querySelectorAll("form");
forms.forEach((value) => {
const post_id = Number(value.getAttribute("data-id"));
value.addEventListener("submit", (e) => {
e.preventDefault(); // prevents reload, still submits form data but views.py does nothing for this data
writeComment(post_id);
});
const requestWrite = new XMLHttpRequest();
const writeComment = (post_id) => {
const form = document.querySelector(`form[data-id="${post_id}"]`);
const author = form.querySelector(".form__author");
const content = form.querySelector(".form__content");
const url = "/write/";
if (author.value && content.value) {
requestWrite.open("POST", url, true);
requestWrite.setRequestHeader(
"Content-Type",
"application/x-www-form-urlencoded"
);
requestWrite.send(
JSON.stringify({
post_id: post_id,
author: author.value,
content: content.value,
})
);
author.value = null;
content.value = null;
}
};
views.py
#csrf_exempt
def write(request):
req = json.loads(request.body)
post_id = req["post_id"]
author = req["author"]
content = req["content"]
comment = Comment.objects.create(parent_post=get_object_or_404(Post, id=post_id), author=author, content=content)
return JsonResponse({"post_id":post_id, "comment_id":getattr(comment, "id"), "author":author, "content":content})
Thank you!
Related
I want to display countries and states in Django form, for that I am trying to get data from json, create form, pass json data to form and get state of the country on ajax request. I managed to write the process as far as I learned, but at last form is not rendered on Django template. How can I render Django form with following code structure?
My Model:
from django.db import models
class Address(models.Model):
country = models.CharField(null=True, blank=True, max_length=100)
state = models.CharField(null=True, blank=True, max_length=100)
def __str__(self):
return '{} {}'.format(self.country, self.state)
My Forms.py:
import json
def readJson(filename):
with open(filename, mode="r", encoding="utf-8") as fp:
return json.load(fp)
def get_country():
""" GET COUNTRY SELECTION """
filepath = './static/data/countries_states_cities.json'
all_data = readJson(filepath)
all_countries = [('-----', '---Select a Country---')]
for x in all_data:
y = (x['name'], x['name'])
all_countries.append(y)
return all_countries
def return_state_by_country(country):
""" GET STATE SELECTION BY COUNTRY INPUT """
filepath = './static/data/countries_states_cities.json'
all_data = readJson(filepath)
all_states = []
for x in all_data:
if x['name'] == country:
if 'states' in x:
for state in x['states']:
y = (state['name'], state['name'])
all_states.append(state['name'])
else:
all_states.append(country)
return all_states
class AddressForm(forms.ModelForm):
country = forms.ChoiceField(
choices = get_country(),
required = False,
label='Country / Region*',
widget=forms.Select(attrs={'class': 'form-control', 'id': 'id_country'}),
)
class Meta:
model = Address
fields = ['country']
My Form.html
<form class="" action="" method="post">
{% csrf_token %}
{% for error in errors %}
<div class="alert alert-danger mb-4" role="alert">
<strong>{{ error }}</strong>
</div>
{% endfor %}
<div class="row">
<div class="col-lg-6">
<div class="mb-4">
{{ form.country}}
</div>
</div>
<div class="col-lg-6">
<div class="mb-4">
<div class="form-group">
<label >Select a Province/State</label>
<select id="id_province" class="form-control" name="state">
<option value="-----">Select Province/State</option>
</select>
</div>
</div>
</div>
</div>
</form>
My Views:
def readJson(filename):
with open(filename, mode="r", encoding="utf-8") as fp:
return json.load(fp)
def return_state_by_country(country):
""" GET STATE SELECTION BY COUNTRY INPUT """
filepath = './static/data/countries_states_cities.json'
all_data = readJson(filepath)
all_states = []
for x in all_data:
if x['name'] == country:
if 'states' in x:
for state in x['states']:
y = (state['name'], state['name'])
all_states.append(state['name'])
else:
all_states.append(country)
return all_states
def getProvince(request):
country = request.POST.get('country')
provinces = return_state_by_country(country)
return JsonResponse({'provinces': provinces})
def processForm(request):
context = {}
if request.method == 'GET':
form = AddressForm()
context['form'] = form
return render(request, './ecommerce/checkout.html', context)
if request.method == 'POST':
form = AddressForm(request.POST)
if form.is_valid():
selected_province = request.POST['state']
obj = form.save(commit=False)
obj.state = selected_province
obj.save()
return render(request, './ecommerce/checkout.html', context)
My Ajax:
<script>
$("#id_country").change(function () {
var countryId = $(this).val();
$.ajax({
type: "POST",
url: "{% url 'ecommerce:get-province' %}",
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'country': country
},
success: function (data) {
console.log(data.provinces);
let html_data = '<option value="-----">Select Province/State</option>';
data.provinces.forEach(function (data) {
html_data += `<option value="${data}">${data}</option>`
});
$("#id_province").html(html_data);
}
});
});
</script>
I am trying to print form.country on template but its not working. What could be the problem?
With ModelForms I find that this type of configuration, in which everything falls under class Meta: works.
However im dubious about that get_country() method. If you share it I can take a deeper look and maybe even test it to make sure that it's not doing anything funky.
If your list of countries is somewhat static and not too long you might wanna consider using a TextChoices enum type in your model attribute to limit the choice selection. Django forms will automatically render a dropdown widget listing the items from your enum.
You can checkout this answer if you want to look into that, which further links to the django docs.
class AddressForm(forms.ModelForm):
class Meta:
model = Address
fields = ['country']
widgets = {
"country": forms.ChoiceField(
choices = get_country(),
attrs={
"class": "form-control",
"id": "id_country"
}
),
}
labels = {
"country" : "Company Country Location"
}
I am new to Django and Javascript - I am making a project that has posts on a site, and I want to create a 'like' button on each post. So far here is my code - I am not getting any errors at all. No console errors in the browser either. But nothing happens. When you click the heart image, if it is liked, it should be a red image. When it is not liked, it is the white heart image. Currently when I click the image, it just stays white. It is always defaulting to that white image. The code doesn't work. Any help is appreciated.
views.py for the like button:
#csrf_exempt
def like(request):
if request.method == "POST":
post_id = request.POST.get('id')
is_liked = request.POST.get('is_liked')
try:
post = Post.objects.get(id=post_id)
if is_liked == 'no':
post.like.add(request.user)
is_liked = 'yes'
elif is_liked == 'yes':
post.like.remove(request.user)
is_liked = 'no'
post.save()
return JsonResponse({'like_count': post.like.count(), 'is_liked': is_liked,
"status": 201})
except:
return JsonResponse({'error': "Post not found", "status": 404})
return JsonResponse({}, status=400)
views.py for the page to show all the posts:
def index(request):
list_of_posts = Post.objects.all().order_by('id').reverse()
paginator = Paginator(list_of_posts, 10)
page_number = request.GET.get('page', 1)
page_obj = paginator.get_page(page_number)
return render(request, "network/index.html", {
"list_of_posts": list_of_posts,
"page_obj": page_obj
})
Javascript file:
like = document.querySelectorAll(".liked");
edit = document.querySelectorAll(".edit");
text_area = document.querySelectorAll(".textarea");
like.forEach((element) => {
like_handeler(element);
});
function like_handeler(element) {
element.addEventListener("click", () => {
id = element.getAttribute("data-id");
is_liked = element.getAttribute("data-is_liked");
icon = document.querySelector(`#post-like-${id}`);
count = document.querySelector(`#post-count-${id}`);
form = new FormData();
form.append("id", id);
form.append("is_liked", is_liked);
fetch("/like/", {
method: "POST",
body: form,
})
.then((res) => res.json())
.then((res) => {
if (res.status == 201) {
if (res.is_liked === "yes") {
icon.src = "https://img.icons8.com/plasticine/100/000000/like.png";
element.setAttribute("data-is_liked", "yes");
} else {
icon.src =
"https://img.icons8.com/carbon-copy/100/000000/like--v2.png";
element.setAttribute("data-is_liked", "no");
}
count.textContent = res.like_count;
}
})
.catch(function (res) {
alert("Network Error. Please Check your connection.");
});
});
}
html file for each post: index.html:
{% extends "network/layout.html" %}
{% load static %}
{% block body %}
<h3> Welcome. Here is your news feed: </h3>
{% for i in page_obj %}
<div class='card mb-3' style="max-width: 530px;" id="card-posts">
<div class="row no-gutters">
<div class="col-md-8">
<div class="card-body">
<h5 class="card-title"><a href="{% url 'profile' username=i.username
%}">{{i.username}}</a></h5>
<p class="card-text">{{i.text}}</p>
<div class="like mt-3">
<img
data-id="{{i.id}}"
id="post-like-{{i.id}}"
class="liked"
{% if not request.user in i.like.all %}
data-is_liked="no"
src="https://img.icons8.com/carbon-copy/100/000000/like--v2.png"
{%else%}
data-is_liked="yes"
src="https://img.icons8.com/plasticine/100/000000/like.png"
{%endif%}
/>
<span id="post-count-{{post.id}}">{{i.like.count}}</span>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
{% block script %}
<script src="{% static 'network/network.js'%}"></script>
{% endblock %}
When I click the 'like' heart button on the page this is what I get. Each instance of 2, happens when I click it once. So this image is from clicking the heart button 3 times:
I am using https://github.com/fengyuanchen/cropper/blob/master/README.md as the cropper function. However, I want to submit field objects (in this case the title) and the cropped image. But I got an error on the admin. And of course, I have done the makemigrations and migrate before running the server
Error Picture
admin.py
from django.contrib import admin
from .models import Image
# Register your models here.
class ImageAdmin(admin.ModelAdmin):
pass
admin.site.register(Image, ImageAdmin)
models.py
from django.db import models
# Create your models here.
class Image(models.Model):
title = models.CharField(max_length=10)
image = models.ImageField(upload_to='images')
def __str__(self):
return str(self.pk)
forms.py
from django import forms
from .models import Image
class ImageForm(forms.Form):
image = forms.ImageField()
title = forms.CharField(
max_length=10,
widget=forms.TextInput(
attrs={
"class": "form-control",
"placeholder": "Title",
},
),
required=True
)
views.py
from django.shortcuts import render, redirect
from .models import Image
from .forms import ImageForm
from django.http import JsonResponse
from django.http import HttpResponseRedirect
def main_view(request):
form = ImageForm()
if request.method == "POST":
form = ImageForm(request.POST, request.FILES)
if form.is_valid():
addimage = Image(
title=form.cleaned_data['title'],
image = form.cleaned_data['image'],
)
addimage.save()
else:
form = ImageForm()
context = {'form': form}
return render(request, 'photo_list.html', context)
photo_list.html
{% extends "base.html" %}
{% block javascript %}
<script>
console.log("Hello");
const imageBox = document.getElementById("image-box");
const confirmButton = document.getElementById("confirm-button")
const input = document.getElementById("id_image");
const csrf = document.getElementsByName("csrfmiddlewaretoken")
const imageForm = document.getElementById("image-form")
input.addEventListener("change", () => {
console.log("change")
const img_data = input.files[0]
const url = URL.createObjectURL(img_data)
imageBox.innerHTML = `<img src="${url}" id="image" width="500px">`
var $image = $('#image');
$image.cropper({
aspectRatio: 16 / 9,
crop: function (event) {
console.log(event.detail.x);
console.log(event.detail.y);
console.log(event.detail.width);
console.log(event.detail.height);
console.log(event.detail.rotate);
console.log(event.detail.scaleX);
console.log(event.detail.scaleY);
}
});
// Get the Cropper.js instance after initialized
var cropper = $image.data('cropper');
confirmButton.addEventListener('click', () => {
cropper.getCroppedCanvas().toBlob((blob) => {
const fd = new FormData()
fd.append('csrfmiddlewaretoken', csrf[0].value)
fd.append('image', blob, 'my-image.png')
console.log("append pass")
$.ajax({
type: "POST",
url: imageForm.action,
enctype: 'multipart/form-data',
data: fd,
success: function (response) {
console.log(response)
},
error: function (error) {
console.log(error)
},
cache: false,
contentType: false,
processData: false,
})
})
})
});
</script>
{% endblock %}
{% block page_content %}
<form action="/cropimage/" id="image-form" method="POST">
{% csrf_token %}
{{form}}
{% comment %} <button class="btn" id="confirm-button"> confirm </button> {% endcomment %}
<input class="btn btn-lg btn-primary btn-block" type="submit" value="Submit" id="confirm-button">
</form>
<div id="image-box" class="mb-3"> </div>
{% endblock %}
I was following this tutorial https://www.youtube.com/watch?v=oWd7SAuCIRM
Basically, I am making an album of images recorded with title, but this time with cropperjs. Any solutions or suggestions would be appreciated :).
Ahh silly me, after a week of finding solutions, I finally realize that I did not put and after deleting some of the database cache.
fd.append('title', $("input[name='title']").val())
on the getCroppedCanvas()
For those who had the same problem, please..
Ref :
How to append more data in FormData for django?
OperationalError, no such column. Django
https://developer.mozilla.org/en-US/docs/Web/API/FormData/append
I'm working on an assignment to use the fetch API to do some of the normal things we would have Python do in our views with JavaScript such as adding records or querying the database. One issue I'm running across is passing the normal properties we would see in Django, say a user or username, where it just shows up as a literal user id when I pull it from the sql database with the fetch API. With the views, html and JavaScript I have written now, how would I go about pulling the username with fetch in JavaScript that I can normally grab with a variable or view with a print statement in the Django console, instead of just viewing the user id from the database. I feel like I'm missing a step and I'm just not seeing it.
urls
app_name = "network"
urlpatterns = [
path("", views.index, name="index"),
path("login", views.login_view, name="login"),
path("logout", views.logout_view, name="logout"),
path("register", views.register, name="register"),
# API Routes
path("addpost", views.post, name="post"),
path("<str:navbar_item>", views.viewposts, name="viewposts"),
]
models.py
class User(AbstractUser):
pass
class Profile(models.Model):
user = models.OneToOneField(User, null=True, on_delete=models.CASCADE)
bio = models.TextField(null=True, blank=True)
# pics
website = models.CharField(max_length=225, null=True, blank=True)
follower = models.ManyToManyField(
User, blank=True, related_name="followed_user") # user following this profile
# profile user that follows this profile
following = models.ManyToManyField(
User, blank=True, related_name="following_user")
def __str__(self):
return f"{self.user}'s' profile id is {self.id}"
def following_users(self):
for username in self.following:
return username
def get_absolute_url(self):
return reverse("network:profile-detail", args=[str(self.id)])
class Post(models.Model):
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name="post_user")
body = models.TextField(max_length=1000)
timestamp = models.DateTimeField(auto_now_add=True)
likes = models.ManyToManyField(User, blank=True, related_name="post_likes")
def __str__(self):
return f"{self.created_by} posted {self.body}"
def get_absolute_url(self):
return reverse("network:post-detail", args=[str(self.id)])
def total_likes(self):
return self.likes.count()
class Meta:
ordering = ["-timestamp"]
views.py
def index(request):
if request.user.is_authenticated:
return render(request, "network/index.html", {})
else:
return HttpResponseRedirect(reverse("network:login"))
#login_required
def post(request):
# Composing a new post must be done via POST
if request.method != "POST":
return JsonResponse({"error": "You must POST your request."}, status=404)
try:
data = json.loads(request.body)
body = data.get("body", "")
user = request.user
print(user)
post = Post(created_by=user, body=body)
# post = Post(created_by=Profile.objects.get(user=user), body=body)
post.save()
except AttributeError:
return JsonResponse({"error": "AttributeError thrown."}, status=500)
return JsonResponse({"message": "Post created."}, status=201)
#login_required
def viewposts(request, navbar_item):
if navbar_item == "viewposts":
posts = Post.objects.all()
posts = posts.order_by("-timestamp")
json_post = serialize("json", posts)
print(posts)
return HttpResponse(json_post, content_type="application/json")
else:
return JsonResponse({"error": "Invalid page."}, status=400)
index.html
{% extends "network/layout.html" %}
{% load static %}
{% block body %}
<div class="container p-5">
{% if error %}
{{ error }}
{% endif %}
<h1 class="display-4">All Posts</h1>
<div class="form-group border rounded p-4">
<h2 class="diplay-3">New Post</h2>
<form id="addpost" class="form-group pt-5">
{% csrf_token %}
<div class="form-group">
<textarea class="form-control" id="body" placeholder="Add post here..."></textarea>
</div>
<input type="submit" class="btn btn-primary"/>
</form>
</div>
<div id="all-posts" class="all-posts">
</div>
</div>
{% endblock %}
{% block script %}
<script src="{% static 'network/main.js' %}"></script>
{% endblock %}
JavaScript
// Post on index page # API Routes /addpost
const addPost = () => {
const addPostUrl = '/addpost';
const csrftoken = getCookie('csrftoken');
const body = document.querySelector('#body').value;
// body needs to be passed into an object before using the stringify method
const bodyObject = { body };
fetch(addPostUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken,
},
body: JSON.stringify(bodyObject)
})
.then(response => response.json())
.then(result => {
console.log(result);
})
.catch(error => {
console.log(error);
});
return false;
};
// Load posts in index page # API Routes /navbar_item
function loadPosts(navItem, event) {
preventPageLoad(event);
const postUrl = `/${navItem}`;
// Send a GET request to the URL to retrieve all posts
fetch(postUrl)
.then(response => response.json())
.then(data => {
data.forEach(post => {
const { fields } = post;
const allPostsContainer = document.querySelector("#all-posts");
const element = document.createElement('div');
const postId = `#post-${fields.id}`;
element.style.textDecoration = 'none';
element.classList.add('HoverClass1');
element.setAttribute('id', `post-${fields.id}`);
element.classList.add('d-flex', 'flex-column' ,'justify-content-between', 'p-4', 'm-3', 'lead', 'border', 'rounded');
element.style.color = '#000000';
element.innerHTML =
// This is returning an id
`<div class="bd-highlight font-weight-bolder mr-5">${fields.created_by}</div>
<div class="bd-highlight">${fields.timestamp}</div>
<div class="flex-fill bd-highlight">${fields.body}</div>`;
console.log(fields);
allPostsContainer.append(element);
const linePost = document.querySelector(postId);
linePost.addEventListener('click', (event) => {
console.log(event);
});
});
})
.catch(error => {
console.log(error);
});
return false;
}
Images showing my admin console in Django versus the browser console and what fetch is pulling in JavaScript. You'll see in the admin console we can view the username, but in the browser console all I'm getting is the user id with fetch.
I figured out how to do this. I added a serialize method to the Post model to convert these properties to JSON.
def serialize(self):
return {
'id': self.id,
'created_by': self.created_by.username,
'body': self.body,
'timestamp': self.timestamp.strftime('%b %-d %Y, %-I:%M %p'),
'likes': self.total_likes()
}
Then in views.py, in my viewposts function, instead of my the HttpResponse, I used JsonResponse and passed the model's serialize method as an argument.
#login_required
def viewposts(request, navbar_item):
if navbar_item == "viewposts":
posts = Post.objects.all()
posts = posts.order_by("-timestamp")
return JsonResponse([post.serialize() for post in posts], safe=False)
else:
return JsonResponse({"error": "Invalid page."}, status=400)
This allowed me to not have to deconstruct anything in my JavaScript file. So I could pull any attributes from my query using dot notation directly off of the data model in fetch.
I have a function in Django that I need to create a confirmation popup for the user... The function contains lots of conditions, I need to create this popup without a huge change in the code. It took a lot of time, I found "return confirm" for the button .. but I can't change the design of the popup.
The function is :
def start(request):
user = request.user
ctx = {
'id-1': request.GET.get('id-1'),
'start_order': StartOrder.objects.filter(
user=user, status='ru'),
}
initial = {
'email': user.email or '',
'password': user.password or '',
}
The initial form should be appear before the confirmation popup
if request.method == 'GET':
ctx['form'] = Form(initial=initial)
ctx['can_create'] = user.can_create()
return render(request, 'orders.html', ctx)
After the confirmation popup:
if request.GET.has_key('id-1'):
test = get_object_or_404(Test, id=request.GET.get('id-1'))
form = Form(request.POST, request=request)
if form.is_valid():
if not user.can_create_order():
return Http404()
ctx['form'] = Form(initial=initial)
ctx['can_create'] = user.can_create_order()
user.email = request.POST.get('email')
user.password = request.POST.get('password')
user.save()
if request.GET.has_key('id-1'):
send_order(test)
ctx['started'] = True
return render(request, 'orders.html', ctx)
orders.html:
{% if id-1 %}
<form class="user-form " method='post'>
{% csrf_token %}
<div class="errors">
{{form.non_field_errors}}
</div>
<div class="form-group">
{% for field in form %}
{{field}}
{{field.errors}}
{% endfor %}
</div>
The button where should the popup appears after click:
<input type='submit' value='Start Automated Order' onclick="return confirm('Are you sure you want to continue?');">
</form>
{% else %}
{% endif %}