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
Related
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')
Hello I am a beginner with the django python framework. I need to display a user's image and bio on a file named user_posts.html in my blog app. I have it where I can access the user's image and bio by looping over the posts for the user. However, I need it so it only displays the bio and the image once. I have a separate profile.html in a users app. In that file, I can do just src="{{ user.profile.image.url }}" and {{ user.profile.bio }} to access the users information but that does not seem to work in my user_posts.html because of the structure of my project. I can't figure out how to tell the for loop to just go over once to access the users information.
users_post.html
{% extends "blog/base.html" %}
{% block content %}
<hr size="30">
<div class="row">
<div class="column left">
{% for post in posts %}
<img style= "float:left" src="{{ post.author.profile.image.url}}" width="125" height="125">
<h5 style="text-align: left;">{{ post.author.profile.bio }}</h5>
{% endfor %}
</div>
views.py
class UserPostListView(ListView):
model = Post
template_name = 'blog/user_posts.html' # <app>/<model>_<viewtype>.html
context_object_name = 'posts'
ordering = ['-date_posted']
paginate_by = 5
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')
models.py
from django.db import models
from django.contrib.auth.models import User
from PIL import Image
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
bio = models.TextField(default='enter bio text here')
def __str__(self):
return f'{self.user.username} Profile'
This is what the problem looks like
Any help is appreciated
Here's how you might do it using get_context_data().
class UserPostListView(ListView):
model = Post
template_name = 'blog/user_posts.html' # <app>/<model>_<viewtype>.html
context_object_name = 'posts'
ordering = ['-date_posted']
paginate_by = 5
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')
def get_context_data(self, **kwargs):
"""
Add User Profile to the template context.
"""
context = super().get_context_data(**kwargs)
profile_user = get_object_or_404(User, username=self.kwargs.get('username'))
context['profile_user'] = profile_user
return context
You'd then use {{ profile_user.profile.bio }} in your template instead of posts for the user profile info.
This is probably better than getting the first object in posts and getting the user profile information from that object in the case the user has no posts yet (but has a bio).
Note, we're fetching the User object both in get_queryset and in get_context_data so this is not super efficient. There are ways around this but I'll leave it to future editors or you to optimize :)
Probably Not Recommended, But Answering Question
Since you initially were just trying to get the first element, here's how I'd do it.
# Method 1
{% with first_post=posts|first %}
{{ posts.user.profile.bio }}
{{ posts.user.profile.image.url }}
{% endwith %}
# Method 2
{{ posts.0.profile.bio }}
{{ posts.0.profile.image.url }}
So I have a web app where the user can enter their information, and eventually I want to display all of it, but at the moment this code right here
class UserPostListView(ListView):
model = Post
template_name = 'mainapp/user_posts.html'
context_object_name = 'posts'
def get_queryset(self):
user = get_object_or_404(User,username=self.kwargs.get('username'))
first_name = get_object_or_404(User,first_name=self.kwargs.get('first_name'))
return Post.objects.filter(author=user).order_by('-published_date')
It gives me an error, and it says User not found.
I have tried add this to the end of the return statement
.order_by('-published_date'),first_name
However this did not work.
This is the relevant urls.py file responsible for the user posts
path('user/<str:username>', UserPostListView.as_view(), name='user-posts'),
This is the UserProfileInfo model
class UserProfileInfo(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=50,blank=True,null=True)
last_name = models.CharField(max_length=50,blank=True,null=True)
description = models.CharField(max_length=150)
image = ProcessedImageField(upload_to='profile_pics',
processors=[ResizeToFill(150, 150)],
default='default.jpg',
format='JPEG',
options={'quality': 60})
joined_date = models.DateTimeField(blank=True,null=True,default=timezone.now)
verified = models.BooleanField( default=False)
def __str__(self):
return f'{self.user.username} Profile'
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
And here is the relevant bit of the user-posts.html
<div class="sidebar">
<p class="active" href="#">{{ view.kwargs.username }}</p>
<button class="commentbtn"><a class="aclass" href="#">Connect with {{ view.kwargs.username }}</a></button>
<p>{{ view.kwargs.first_name }}</p>
<p>Lorem</p>
</div>
I want to be able to display the first name of the person in the ```{{ view.kwargs.first_name }}, however everything I have tried has failed to work
I expected no errors to occur and for this to work, however this is not the case. Is it possible to have 2 get_queryset methods, or is there something I can do in the current one to achieve my goal of displaying the information of the user
Thanks for any help you can give :)
How about this?
def get_queryset(self):
qs = super().get_queryset() #this is the same as Post.objects.all()
user = self.request.user
return qs.objects.filter(author=user).order_by('-published_date')
Now you can access this query using object_list on your template, but since you changed it's name with context_object_name='posts', use posts instead:
{% for post in posts %}
<h1>{{ post.first_name }}</h1>
...
{% endfor %}
But it looks like the Post model isn't going to have a first_name column?
You might be better off using the request.user object here as well:
<h1>{{ request.user.first_name }}</h1>
<h2>{{ request.user.username }}</h2>
I want two models from different apps to be rendered in one template. For example in my home page there are list of posts showing post author and post content accordingly. When I will click on author tag I want to show author profile at the top and list of posts by that author bellow. I am using class based view (ListView andd DetailView).
In my 'users app' I have Profile model and in 'blog app' I have Post model.
blog/models.py
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})
users/models.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
phone = models.CharField(max_length=10, blank=True)
def __str__(self):
return f'{self.user.username} Profile'
users/views.py
class ProfileDetailView(DetailView):
model = Profile
# to get two model in one page
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['posts'] = Post.objects.all()
return context
my template
<div class="">
<legend class="border-bottom mb-4">Profile Info</legend>
<div class="media">
<img class="rounded-circle account-img" src="{{ object.image.url }}">
<div class="media-body">
<h2 class="account-heading">{{object.user.username}}</h2>
<p class="text-secondary">{{object.user.email}}</p>
<p class="text-secondary">{{object.user.profile.phone}}</p>
</div>
</div>
</div>
<div class="">
<legend class="border-bottom mb-4">User's Posts</legend>
<h2 class="account-heading">{{?????}}</h2>
<p class="text-secondary">{{??????}}</p>
</div>
urls.py
path('profile/<int:pk>/', views.ProfileDetailView.as_view(),
name='profile-detail'),
So, my question is how to make queries to get posts from selected/clicked user. User Profile part is working fine. But cannot get data from post model.
You need to extend your User model and add a get_absolute_url() method, which will return a relative or an absolute path to your author foreign key while rendering post.
See the official django docs: https://docs.djangoproject.com/en/2.2/ref/models/instances/#django.db.models.Model.get_absolute_url
{% for each in posts %}
<li>{{each.title}} | Written by {{each.author}}</li>
{% endfor %}
Since v1.7 (see release notes) django.contrib.auth.models.AbstractUser no longer defines a get_absolute_url() method.
As a consequence you need to create the get_absolute_url method for your User model so that you can use {{ user.get_aboslute_url}} in your templates.
This can be done either through a custom User model as dotslash227 suggest in his answer, or by adding the ABSOLUTE_URL_OVERRIDES to your settings, which might be somewhat simpler.
Ether way you then can {{ user.get_aboslute_url }} in your template (more like {{each.author.get_absolute_url}} in your case).
For the latter case, the settings part might look something like:
ABSOLUTE_URL_OVERRIDES = {
'auth.user': lambda u: "/users/%s/" % u.username,
}
Obviously, in any case, you still need to define a view for this pattern.
You can access the current user in your DetailView class using self.request.user
Hence to get a specific user's posts, this is what you might looking for -
context['posts'] = Posts.objects.filter(author=self.request.user) since author is a foreign key pointing to the Users model.
instead of
context['posts'] = Post.objects.all()
This should give you the posts of the current user. Let me know if you need clarification with anything.
Finally I got my answer. Thanks to all who tried to help me.
Problem was in querying. Comment from rohit keshav was a hint to my problem. Following is the code:
users/views.py
class ProfileDetailView(DetailView):
model = Profile
# to get two model in one page
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['posts']=Post.objects.filter(author=self.object.user)
return context
I want a public page showing the profile of a specific user (so I cannot grab the user id from the logged-in users). I'm not able to select their specific compositions.
I am using a custom user model, so that I have a User class and then a Profile class which is linked to the User via OneToOneField (see code below). I also have a Composition class, which is linked to a specific composer via a ForeignKey. I am able to get the details of a specific profile and I'm also able to print out all the compositions (using Composition.objects.all()).
My models.py:
class User(AbstractBaseUser):
email = models.EmailField(unique=True, max_length=255)
full_name = models.CharField(max_length=255, blank=True, null=True)
objects = UserManager()
[...]
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
[...]
def get_absolute_url(self):
return reverse('profile', args=[str(self.id)])
class Composition(models.Model):
title = models.CharField(max_length=120) # max_length = required
description = models.TextField(blank=True, null=True)
composer = models.ForeignKey(
USER, #my custom user model
on_delete=models.CASCADE
)
def get_absolute_url(self):
return reverse("composition-detail", kwargs={"id": self.id})
def __str__(self):
return "%s" % (self.title)
My views.py:
def profile_details(request, id):
compositions = Composition.objects.filter(composer__id=id)
context = {
"object_list": compositions
}
return render(request, "profile.html", context)
My urls.py:
path('profile/<int:id>/', views.profile_details, name='profile')
My template.html:
{% extends "base.html" %}
{% block content %}
<div class="content-section">
<div class="media">
<img class="rounded-circle account-img" src="{{ user.profile.image.url }}">
<div class="media-body">
<h2 class="account-heading">{{ user.full_name }}</h2>
<p>{{ obj.profile_id}}</p>
<p class="text-secondary">{{ user }}</p>
{% for composition in object_list %}
<li>{{ composition.title }}</li>
{% endfor %}
</div>
</div>
</div>
{% endblock content %}
I'm expecting to see the compositions by that specific composer, i.e. user associated to the profile I'm viewing.
Edit
I've made some progress by adding some custom data to my view:
class ProfileDetails(generic.DetailView):
model = Profile
template_name = 'profile.html'
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['compositions'] = Composition.objects.filter(id=1)
return context
Now the context contains indeed all the composition. How do I filter only the composition by the user connected to the profile I'm viewing?
After many tests I've apparently finally come to a solution.
My views.py:
def profile_details(request, pk):
compositions = Composition.objects.filter(composer__id=pk)
context = {
"compositions": compositions,
}
return render(request, "profile.html", context)
And my urls.py:
path('profile/<int:pk>/', views.profile_details, name='profile')
The pk, which is the same for User and Profile objects, is taken from the URL, and used for filtering the Composition objects and find only the compositions by that composer.