I am working on a blog website project. I am using Django crispy form to create blog posts. Users can post articles by clicking the post button. On the add post page, users have to provide title, content, image. User also have to select category.
blog/model.py
from django.db import models
from django.utils import timezone
from django.contrib.auth import get_user_model
from django.urls import reverse
# Create your models here.
class Category(models.Model):
cid = models.AutoField(primary_key=True, blank=True)
category_name = models.CharField(max_length=100)
def __str__(self):
return self.category_name
class Post(models.Model):
aid = models.AutoField(primary_key=True)
image = models.ImageField(null=True, blank=True, default='blog-default.png', upload_to='images/')
title = models.CharField(max_length=200)
content = models.TextField()
created = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
cid = models.ForeignKey(Category, on_delete=models.CASCADE)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk':self.pk})
views.py
from django.shortcuts import render
from .models import Post
from django.views.generic import CreateView
from django.contrib.auth.mixins import LoginRequiredMixin
class UserPostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'content', 'image', 'cid']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
post_form.py
{% extends 'users/base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Add Post</legend>
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button type="submit" class="btn btn-outline-primary btn-sm btn-block"> Post </button>
</div>
</form>
</div>
{% endblock content %}
Here, the .html file in the browser shows that the label of the category is 'Cid'. But I want this label as 'Select Category'. How can I change this?
UI
Django form...........
You can add a verbose_name to your field in the models.py. That is the value that is displayed when generating forms. Like this:
cid = models.ForeignKey(Category, on_delete=models.CASCADE, verbose_name='Select Category')
You can use this method in forms.py:
class form(forms.ModelForm):
class Meta:
....
labels={
'cid': 'Select Category',
}
....
or
cid = forms.Select(label='Select Category')
Related
I am new to Django, and I am wondering how I can intelligently link the system of comments and posts using class-based views. Here's my 'models.py' file in the 'blog' app:
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
from django.urls import reverse
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})
class Comment(models.Model):
post = models.ForeignKey(Post, related_name='comments', on_delete=models.CASCADE)
title = models.CharField(max_length=100)
author = models.ForeignKey(User, on_delete=models.CASCADE)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
class Meta:
ordering = ['date_posted']
def __str__(self):
return '{} - {}'.format(self.author, self.date_posted)
There's my 'post_detail.html' template that shows the specific post and here I just want to show all comments under the post:
{% extends 'blog/base.html' %}
{% block content %}
<article class="media content-section">
<img class="rounded-circle article-img" src="{{ object.author.profile.image.url }}">
<div class="media-body">
<div class="article-metadata">
<a class="mr-2" href="{% url 'user-posts' object.author.username %}">
{{ object.author }}
</a>
<small class="text-muted">{{ object.date_posted | date:"F d, Y" }}</small>
{% if object.author == user %}
<div>
<a class="btn btn-secondary btn-sm mt-1 mb-1" href="{% url 'post-update' object.id %}">Update</a>
<a class="btn btn-danger btn-sm mt-1 mb-1" href="{% url 'post-delete' object.id %}">Delete</a>
</div>
{% endif %}
</div>
<h2 class="article-title">
{{ object.title }}
</h2>
<p class="article-content">{{ object.content }}</p>
</div>
</article>
<div>
<strong><h2>Comments Section</h2></strong>
</div>
{% endblock content %}
'views.py' file looks like this:
from blog.forms import CommentForm
from django.db import models
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.mixins import UserPassesTestMixin
from django.contrib.auth.models import User
from django.shortcuts import redirect, render
from django.shortcuts import get_object_or_404
from django.views.generic import ListView
from django.views.generic import DetailView
from django.views.generic import CreateView
from django.views.generic import UpdateView
from django.views.generic import DeleteView
from .models import Post
from .models import Comment
def home(request):
context = {
'title': 'Home',
'posts': Post.objects.all()
}
return render(request, 'blog/home.html', context)
class PostListView(ListView):
model = Post
template_name = 'blog/home.html'
context_object_name='posts'
ordering = ['-date_posted']
paginate_by = 7
class UserPostListView(ListView):
model = Post
template_name = 'blog/user_posts.html'
context_object_name='posts'
paginate_by = 7
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(author=user).order_by('-date_posted')
class PostDetailView(DetailView):
model = Post
def comment(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.save()
return redirect('post-detail', pk=post.pk)
else:
form = CommentForm()
return render(request, 'blog/post_detail.html', {'form': form})
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'content']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Post
fields = ['title', 'content']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def test_func(self):
post = self.get_object()
if self.request.user == post.author:
return True
return False
class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Post
success_url = '/'
def test_func(self):
post = self.get_object()
if self.request.user == post.author:
return True
return False
def about(request):
return render(request, 'blog/about.html', {'title': 'About'})
I understand that everything should be done in the PostDetailView class but I have no clue how to start. I will appreciate any ideas or suggestions
You have a foreign key from Comment to Post, so if you have a Post object, you can get all related comments with post.comments.all().
You can do that directly in the template, you just have to omit the parenthesis. Here is a template snippet for illustration:
<div>
<strong><h2>Comments Section</h2></strong>
<ul>
{% for comment in object.comments.all %}
<li>{{ comment }} {{ comment.context }}</li>
{% endfor %}
</ul>
</div>
This kind of hides the database query to fetch the comments in the template. If you'd rather perform the queries in the view, you can overwrite PostDetailView.get_context_data():
class PostDetailView(DetailView):
model = Post
def get_context_data(self, **kwargs):
ctx = super().get_context_datat(**kwargs)
ctx["comments"] = ctx["object"].comments.all()
return ctx
In this case, the for-loop in the template would look like this:
{% for comment in object.comments.all %}
I need help with my code. I have read through the code several times and I didn't see anything wrong with it.
The user is expected to submit a job application and redirect the user to the dashboard, but it did not submit the job application neither does it direct the user to the dashboard.
here is my code:
mode.py
from django.db import models
from django.contrib.auth.models import User
class Job(models.Model):
title = models.CharField(max_length=255)
short_description = models.TextField()
long_description = models.TextField(blank=True, null=True)
created_by = models.ForeignKey(User, related_name='jobs', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
changed_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
class Application(models.Model):
job = models.ForeignKey(Job, related_name='applications', on_delete=models.CASCADE)
content = models.TextField()
experience = models.TextField()
created_by = models.ForeignKey(User, related_name='applications', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
Views.py
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from .forms import AddJobForm, ApplicationForm
from .models import Job
def job_detail(request, job_id):
job = Job.objects.get(pk=job_id)
return render(request, 'jobs/job_detail.html', {'job': job})
#login_required
def add_job(request):
if request.method == 'POST':
form = AddJobForm(request.POST)
if form.is_valid():
job = form.save(commit=False)
job.created_by = request.user
job.save()
return redirect('dashboard')
else:
form = AddJobForm()
return render(request, 'jobs/add_job.html', {'form': form})
#login_required
def apply_for_job(request, job_id):
job = Job.objects.get(pk=job_id)
if request.method == 'POST':
form = ApplicationForm(request.POST)
if form.is_valid():
application = form.save(commit=False)
application.job = job
application.created_by = request.user
application.save()
#create_notification(request, job.created_by, 'application', extra_id=application.id)
return redirect('dashboard')
else:
form = ApplicationForm()
return render(request, 'jobs/apply_for_job.html', {'form': form, 'job': job})
forms.py
from django import forms
from .models import Job, Application
class AddJobForm(forms.ModelForm):
class Meta:
model = Job
fields = ['title','short_description','long_description']
class ApplicationForm(forms.ModelForm):
class Meta:
model = Application
fields = ['content', 'experience']
apply_for_job.html
{% extends 'core/base.html' %}
{% block content %}
Apply for job - {{ job.title }}
<form method="post" action=".">
{% csrf_token %}
{% if form.errors %}
{% for error in form.errors %}
<div class="notification is-danger">
{{ error }}
</div>
{% endfor %}
{% endif %}
<div class="field">
<label>Content</label>
<div class="control">
<textarea class="textarea" name="content" id="id_content"></textarea>
</div>
</div>
<div class="field">
<label>Experience</label>
<div class="control">
<textarea class="textarea" name="experience" id="id_experience"></textarea>
</div>
</div>
<div class="field">
<div class="control">
<button class="button is-success">Submit</button>
</div>
</div>
</form>
{% endblock %}
what could be wrong with my code?
the error in this line
<form method="post" action=".">
just delete the point, and it should work,
<form method="post" action="">
I created model Post and Comment, now I want to display comments from Post but I have a problem. In tutorial there is a line of code in html {% for comment in post.comments.all %} but isn't working in my app. If I set {% for comment in comments %} it works but displays all comments from models (I want only comments from the post). How to fix that? Below I pasted my code.
models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=64, blank=False, null=False)
short_text = models.TextField(blank=False, null=False)
text = models.TextField(blank=False, null=False)
image = models.TextField(blank=False, null=False)
date = models.DateTimeField(auto_now_add=True, blank=True)
views = models.IntegerField(default=0)
class Comment(models.Model):
post = models.ForeignKey('main.Post', on_delete=models.CASCADE)
author = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(auto_now_add=True, blank=True)
approved_comment = models.BooleanField(default=False)
def approve(self):
self.approved_comment = True
self.save()
def __str__(self):
return self.text
views.py
from django.shortcuts import get_object_or_404, render, redirect
from .models import Post, Comment
def index_pl(request):
posts = Post.objects.all()
return render(request, 'index_pl.html', {'posts': posts})
def single_article_pl(request, id):
posts = get_object_or_404(Post, pk=id)
posts.views = posts.views + 1
posts.save()
comments = Comment.objects.all()
return render(request, 'single_article_pl.html', {'posts': posts, 'comments': comments})
html
{% for comment in post.comments.all %}
<div class="comment">
<div class="date">{{ comment.created_date }}</div>
<strong>{{ comment.author }}</strong>
<p>{{ comment.text|linebreaks }}</p>
</div>
{% empty %}
<p>No comments here yet :(</p>
{% endfor %}
admin.py
from django.contrib import admin
from .models import Post, Comment
admin.site.register(Post)
admin.site.register(Comment)
In tutorial there is a line of code in html {% for comment in post.comments.all %} but isn't working in my app.
This is likely because they specified the related_name=… parameter [Django-doc] in the ForeignKey from Comment to Post, like:
# Option 1: set the related_name to 'comments'
class Comment(models.Model):
post = models.ForeignKey(
'main.Post',
related_name='comments',
on_delete=models.CASCADE
)
# …
The related_name=… specifies the name of the relation in reverse, so in this case accessing the Comments of a given Post object. By default this is model_name_set, so in this case comment_set.
You thus can either specify a related name; or you can acces the comment_set manager:
Option 2: use the default related_name
{% for comment in post.comment_set.all %}
<div class="comment">
<div class="date">{{ comment.created_date }}</div>
<strong>{{ comment.author }}</strong>
<p>{{ comment.text|linebreaks }}</p>
</div>
{% empty %}
<p>No comments here yet :(</p>
{% endfor %}
I was able to save before without an issue using django.views.generic.CreateView. However, I can't save the same form using View.
Below are my codes:
models.py
class Project(models.Model):
name = models.CharField(max_length=200, verbose_name='Project Name')
user = models.ForeignKey(User,
on_delete=models.CASCADE,
blank=True,
null=True)
client = models.ForeignKey(Client,
on_delete=models.SET_NULL,
blank=True,
null=True)
deadline = models.DateTimeField(blank=True, null=True)
views.py
from django.views.generic import View
class ProjectCreateView(LoginRequiredMixin, View):
form_class = ProjectCreateForm
success_url = reverse_lazy('dashboard')
template_name = 'translation/project_form.html'
def get(self, request, *args, **kwargs):
form = self.form_class()
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
project_form = self.form_class(request.POST, instance=request.user)
if project_form.is_valid():
project_form.save()
return redirect(self.success_url)
else:
return render(request, self.template_name)
html
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<div class="container mt-5 py-2 w-50 bg-dark text-white">
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn btn-success mt-3">Confirm</button>
</form>
</div>
{% endblock content %}
urls.py
path('project/new', views.ProjectCreateView.as_view(), name='project-add'),
When I submit the form in html, it doesn't render any error message. It simply redirects to the self.success_url and the intended object is not saved.
Any help or advice will be much appreciated. Thank you!
Edit:
Forgot to include the codes for the Form:
forms.py
class ProjectCreateForm(forms.ModelForm):
class Meta:
model = Project
widgets = {
'deadline' : forms.DateInput(attrs={'type':'date'})
}
fields = ['name', 'client', 'deadline']
I am learning Django. Currently I build my blog project. I want to add function to filter posts by date (you can choose specific date from combo box and click "filter" button and then on the main page will display only this posts which were created in this date). Since I'm still new to django, I'm struggle how to handle it.
My question is how to build a functionality that will extract the sent date from the combo box and pass it to the view where I will do the appropriate filtering in the get_queryset method. Below I publish my code:
Part of my .html file where I build combo box:
<p class='text-muted'>Choose date from the list below.</p>
<form method="GET">
<select name="date_filter">
<option>-----------------</option>
{% for post in posts %}
<option>{{ post.date_posted }}</option>
{% endfor %}
</select>
<button type="submit" class="btn btn-info btn-sm mt-1 mb-1">Filter</button>
</form>
I would also like each date to be unique and displayed only once. Currently, each date is displayed as many times as many posts were created that day because DateTimeField in my model also stores the post creation hour.
Main page view where post are displayed - in my views.py file:
class PostListView(ListView):
model = Post
template_name = "blog_app/home.html"
context_object_name = 'posts'
ordering = ['-date_posted']
# I believe here should be something which fetch choice from combo box and asign it to the
# date_from_combo_box variable. Please, correct me if I'm wrong.
def get_queryset(self):
# Here I will decide what posts are to be displayed based on the selection made in the combo box
if self.date_posted == date_from_combo_box:
return Post.objects.filter(date_posted=date_from_combo_box)
My models.py file:
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
from django.urls import reverse
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})
class Comment(models.Model):
comm_content = models.TextField()
add_date = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
def __str__(self):
return f"Comment of post {self.post} posted at {self.add_date}."
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.post.pk})
Thanks for any hints and advice.
For getting unique date from your post table for dropdown change your query to
Post.objects.dates('date_posted', 'day').distinct()
Change your html
<p class='text-muted'>Choose date from the list below.</p>
<form action="url_to_list_view" method="GET">
<select name="date_filter">
<option>-----------------</option>
{% for post in posts %}
<option>{{ post.date_posted }}</option>
{% endfor %}
</select>
<button type="submit" class="btn btn-info btn-sm mt-1 mb-1">Filter</button>
</form>
Your listview will look like this.
class PostListView(ListView):
model = Post
template_name = "blog_app/home.html"
context_object_name = 'posts'
ordering = ['-date_posted']
def get_queryset(self):
search = self.request.GET.get('date_filter', None)
if search is not None:
return Post.objects.filter(date_posted__date=search)
else:
return Post.objects.all()
You could use something like this,
Model.objects.filter(date_attribute__month=month, date_attribute__day=day)
or for a range you can use
Sample.objects.filter(date__range=["2011-01-01", "2011-01-31"])
Credits