I'm doing a Django blog website project.
One of my features is when you log in, you can click on the tab Other members to show you a list of other members; like this picture
.
However, as of now, this feature only works when I login as one specific account which is an admin. When I log in as other members (not the admin) or another admin account, the feature is not working; that means when I click the tab Other Members it will display errors like
"Friend matching query does not exist"
I'm really confused and getting stuck on this one. Any ideas how to solve it?
Here is my code:
**list_users.html **
{% extends "blog/base.html" %}
{% block content %}
<div class="container">
<div class="col-sm-6">
<h2>Other People</h2>
{% for user in users %}
<a href="{% url 'view_profile_with_pk' pk=user.pk %}">
<h3>{{ user.username }}</h3>
</a>
{% if not user in friends %}
<a href="{% url 'change_friends' operation='add' pk=user.pk %}">
<button type="button" class="btn btn-success">Add Friend</button>
</a>
{% endif %}
{% endfor %}
</div>
<br>
<div class="col-sm-6">
<h2>Friends</h2>
{% for friend in friends %}
<a href="{% url 'view_profile_with_pk' pk=friend.pk %}">
<h3>{{ friend.username }}</h3>
</a>
<a href="{% url 'change_friends' operation='remove' pk=friend.pk %}">
<button type="button" class="btn btn-default">Remove Friend</button>
</a>
{% endfor %}
</div>
</div>
{% endblock content %}
views.py function listusers
def listusers(request):
posts = Post.objects.all().order_by()
users = User.objects.exclude(id=request.user.id)
friend = Friend.objects.get(current_user=request.user)
friends = friend.users.all()
context = {
'posts': posts, 'users': users, 'friends': friends
}
return render(request, 'blog/list_users.html', context)
model.py class Friend
class Friend(models.Model):
users = models.ManyToManyField(User)
current_user = models.ForeignKey(User, related_name='owner', null=True, on_delete=models.CASCADE)
It's because you are using get operator to get the friend. The problem as the image you provided shows, is in line 25 of your views. in below method:
friend = Friend.objects.get(current_user=request.user)
you are trying to get an item which does not exist. There is no friend that it's current_user is the requested user.
you can fix it like this:
friend = Friend.objects.filter(current_user=request.user).first()
if friend:
friends = friend.users.all()
else:
friends = User.objects.none()
Try to use filter instead of get . get throws exception when there's no matching result.
Have a look at filter
Friend.objects.filter(current_user=request.user).first()
Another approach is to create a Friend instance if a user does not have one. You can use get_or_create for this. For example:
users = User.objects.exclude(id=request.user.id)
friend, created = Friend.objects.get_or_create(current_user=request.user) # or have other necessary fields for Friend field
friends = friend.users.all()
context = {
'posts': posts, 'users': users, 'friends': friends
}
return render(request, 'blog/list_users.html', context)
Related
In my blog app I want to allow unkown users to see articles, but I also want to allow logged users to see in the same page (somewhere else) their own articles; something like:
YOUR ARTICLES: list (only if user is logged)
ALL ARTICLES: list
Note that I need to show articles based on the user logged in because the url must be this:
path('<int:user_id>/', views.IndexView.as_view(), name='index'),
index.html:
{% if user.is_authenticated %}
Your articles:
<div class="container py-5">
{% if article_list %}
{% for article in article_list %}
<div class="container">
<div class="row">
<div class="col">
{{article.author}}
</div>
<div class="col">
{{article.title}}
</div>
<div class="col">
{{article.pub_date}}
</div>
<a href=" {% url 'blog_app:detail' user_id = user.id %} ">
<div class="col">
Open article
</div>
</a>
</div>
</div>
{% endfor %}
{% else %}
<b>No articles!</b>
{% endif %}
</div>
{% endif %}
views.py:
class IndexView(ListView):
model = Article
template_name = 'blog_app/index.html'
context_object_name = 'article_list'
#return articles of a particular author
def get_queryset(self):
self.article = get_object_or_404(Article, author_id=self.kwargs['user_id'])
return Article.objects.filter(
author = self.article.author
)
My question is: How can I get from IndexView two different querysets? One with all articles and one with articles filtered by author?
Bonus question:
Can I allow unkown users to reach the articles page if the url needs to specify the user id?
After answers, this is one possible correct solution (don't focus on year and month filters, I added them but obviusly aren't related to the solution):
class IndexView(ListView):
model = Article
template_name = 'blog_app/index.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['all_article_list'] = Article.objects.all()
context['author_article_list'] = Article.objects.filter(
pub_date__year = self.kwargs['year'],
pub_date__month = self.kwargs['month'],
author = self.kwargs['user_id']
).order_by('-pub_date')
return context
In django templates I used these context names to iter articles:
Author articles:
{% if user.is_authenticated %}
{% if author_article_list %}
{% for article in author_article_list %}
...
{% endfor %}
{% endif %}
{% endif %}
All articles:
{% if all_article_list %}
{% for article in all_article_list %}
...
{% endfor %}
{% endif %}
You need to specify:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['all_articles'] = Article.objects.all()
return context
Then you can also use an if statement in th template, to check if the {{all_articles}} exists.
"Can I allow unkown users to reach the articles page if the url needs to specify the user id?"
Unauthenticated users do not have an ID, this will result in an error. If you want users to go to the author of the current article being viewed, wouldn't it be {{article.author.id}}? (Not sure if this is what you want.)
Just use a standard context. Add this metod to you view (changing names, obviously):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['book_list'] = Book.objects.all()
return context
Bonus answer:
Well, anyone can enter every instance of such view. The only thing would be to change manually number in the browser, i.e. anyone can access this link:
http://example.com/1/
But if someone is not authenticated, that link: <a href=" {% url 'blog_app:detail' user_id = user.id %} "> would raise error, but of course cause of {% if user.is_authenticated %} it's not rendered anyway.
You need to set proper permissions to your view.
I think you can also override the get_queryset() method according to different conditions, so:
class IndexView(ListView):
model = Article
template_name = 'blog_app/index.html'
context_object_name = 'article_list'
def get_queryset(self):
qs=super().get_queryset()
if self.request.user.is_authenticated:
article=get_object_or_404(Article,author_id=self.kwargs['user_id'])
return qs.filter(author=article.author) #filtered queryset
else:
return qs #default queryset
I am making a website that allows students to find upcoming study sessions for their courses. I am doing this in Django and HTML. A student uploads their courses to the site and they are shown on the courses page as buttons (ex. CS 101 - Intro to CS). When a student clicks on one of their courses (button), it is supposed to bring them to a page that shows available study sessions for that course. I am stuck because I do not know how to properly filter the available study sessions on the next page based on which course is clicked. Is there a way to store the info of the course as a variable so when the button is clicked I can use that variable to filter the results? EDIT: I have made these changes and now I am getting a ValueError too many values to unpack expected 2. I am almost certain it is happening in my views.
Here is the page that shows a user's courses:
<div class="container h-100" style="top:50%; bottom:50%; width:100%;">
<div class="row">
{% if courses_list %}
{% for course in courses_list %}
<a type="button" class="btn btn-outline-secondary" href="{% url 'study:course-session'%}" >{{ course.subject }} {{ course.number}}-{{course.name}} </a>
<br><br><br>
{% endfor %}
{% else %}
<p class="text-center">You have not added any courses yet!</p>
{% endif %}
</div>
</div>
And here is the page that I am trying to filter the list of study sessions (which have a field course that is a ForeignKey to the Courses model):
<h1><center>Upcoming Study Sessions</center></h1>
<div>
<a class="btn btn-success" style="position:absolute; margin-right:2px; top:15%; right:0;" href="{% url 'study:courses' %}" role="button" >Back to My Courses</a>
</div>
<br><br>
<div class="container h-100" style="top:50%; bottom:50%; width:100%;">
<div class="row">
<button type="button" class="btn btn-outline-secondary" >Date/Time: {{ session.date }} <br> Location: {{ session.location }} </button>
<br><br><br>
</div>
</div>
View for the template:
def CourseSessionView(request, course_pk):
course_wanted = Course.objects.get(id=course_pk)
try:
return Study.objects.filter(course=course_wanted)
except:
return messages.error(request, 'There are no upcoming study sessions at this time for the requested course.')
Model for course and session:
class Course(models.Model):
SUBJECT_CHOICES = [
('AAS', 'AAS')
]
subject = models.CharField(
max_length=4, choices=SUBJECT_CHOICES, default='')
number = models.PositiveSmallIntegerField(
validators=[MaxValueValidator(9999)], default=0)
name = models.CharField(max_length=100, default='')
roster = models.ManyToManyField(
Student, blank=True, related_name="courses")
# Use [Student object].courses.all() to see all of a student's courses
def __str__(self):
return f"{self.subject} {self.number} - {self.name}"
class Study(models.Model):
organizer = models.ForeignKey(Student, on_delete=models.CASCADE)
date = models.DateTimeField()
# Use [Student object].studies.all() to see all of a student's study sessions
attendees = models.ManyToManyField(Student, related_name="studies")
location = models.CharField(max_length=30)
course = models.ForeignKey(Course, on_delete=models.CASCADE)
def __str__(self):
return f"{self.date} - {self.location}"
Url:
path('<int:course_pk>/sessions/',
views.CourseSessionView, name='course-session')
Note: The function based views' name doesn't require to be in PascalCase as in your case, it should be in snake_case.
The page that show the user's courses, there you need to pk of courses:
<div class="container h-100" style="top:50%; bottom:50%; width:100%;">
<div class="row">
{% if courses_list %}
{% for course in courses_list %}
<a type="button" class="btn btn-outline-secondary" href="{% url 'study:course-session' course.pk %}" >{{ course.subject }} {{ course.number}}-{{course.name}} </a>
<br><br><br>
{% endfor %}
{% else %}
<p class="text-center">You have not added any courses yet!</p>
{% endif %}
</div>
</div>
Your view for the template, i am defining it in snake_case, since its recommended way.
def course_session(request, course_pk):
course_wanted = Course.objects.get(id=course_pk)
study_courses=''
try:
study_courses= Study.objects.filter(course=course_wanted)
except:
messages.error(request, 'There are no upcoming study sessions at this time for the requested course.')
else:
return render(request,'anyfolder/anyfile.html',{'study_courses':study_courses})
return render(request,'anyfolder/anyfile.html') #then it will show only your error message.
Your url in urls.py be like:
path('any_route_name/<int:course_pk>/', views.course_session, name='course_session')
Note: Never forget to pass / at the end of your url or route_name.
Then, in your any template file you can access it and run loop:
{% for study in study_courses %}
{{study.organizer}},{{study.date}}
{% endfor %}
Then, you can access all its properties, and take benefit of ManyToOne relation.
This is going to be a very general type of answer since you are not providing your models or your views, but I think the idea would be the following.
First, in your template you can pass a parameter for the course number in the url:
your_template.html
<a class="btn btn-outline-secondary"
href="{% url 'study:course-session' course.pk %}">
{{ course.subject }} {{ course.number}}-{{course.name}}
</a>
Then in your view you can access that value, and from it get the course:
views.py
def the_view_name(request, course_pk):
# Here you now have access to the course's primary key, pk, so you can get the
# course and filter the study sessions by that course, etc...
You will need to modify the urls.py so the view can accept this new parameter:
urls.py
path('the_view_name/<int:course_pk>', views.the_view_name, name='the_view_name'),
EDIT
Make the following changes:
First to your views.py:
def CourseSessionView(request, course_pk):
try:
course_wanted = Course.objects.get(id=course_pk)
except:
return messages.error(request, 'course not found')
study_sessions = Study.objects.filter(course=course_wanted)
if study_sessions.count() < 1:
return messages.error(request, 'There are no upcoming study sessions at this time for the requested course')
context = {
'study_sessions': study_sessions,
}
return render(request, 'study/your_template_file.html', context)
Then in your html
<h1><center>Upcoming Study Sessions</center></h1>
<div>
<a class="btn btn-success" style="position:absolute; margin-right:2px; top:15%; right:0;" href="{% url 'study:courses' %}" role="button" >Back to My Courses</a>
</div>
<br><br>
<div class="container h-100" style="top:50%; bottom:50%; width:100%;">
{% for session in study_sessions %}
<div class="row">
<button type="button" class="btn btn-outline-secondary" >Date/Time: {{ session.date }} <br> Location: {{ session.location }} </button>
<br><br><br>
</div>
{% endfor %}
</div>
When I run the following code, it is showing a DoesNotExist error at 'post_connected'.
this is my views.py
from django.shortcuts import render,redirect
from django.contrib.auth.decorators import login_required
from users.models import profile
from .models import Follow, post, like_post
from django.contrib.auth.models import User
from django.core.paginator import Paginator
def home(request):
user = request.user
info = profile.objects.get(user=user)
follow1 = Follow.objects.filter(follow_user=user)
follow2 = Follow.objects.filter(user=user)
follow_by = follow1.count()
follow_to = follow2.count()
follows = [user]
for obj in follow2:
follows.append(obj.follow_user)
posts = post.objects.filter(author__in=follows).order_by('-date')
paginator = Paginator(posts, 10)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
count = posts.count()
postid = request.POST.get('post_id')
post_connected = post.objects.get(id=postid)
mypost = like_post.objects.filter(user=user, liked=post_connected)
if mypost.exists():
liked = False
else:
liked = True
context = {
'user':user,
'liked':liked,
'post':posts,
'page_obj': page_obj,
'follow_by':follow_by,
'follow_to':follow_to,
}
return render(request, 'home.html', context)
now, this is my template view
{% for posts in page_obj %}
<div class="posts">
<img src="{% static 'logo.png' %}" style="width: 17px; height:14px;">
{{posts.author}}<br><br>
<span style="font-weight:bold; font-size:17px;">{{posts.content}}</span><br><br>
<form action="/like/{{posts.id}}/" method="POST">
{% csrf_token %}
<input type="text" value="{{posts.id}}" name="post_id" style="display: none;">
<button style="background-color: inherit; border:none; outline:none">
{% if liked %}
<i class="fa fa-heart" aria-hidden="true" style="color: red;"></i>
{% else %}
<i class="fa fa-heart" aria-hidden="true" style="color: lightgrey;"></i>
{% endif %}
</button>
<span style="font-size:12px; float:right"><b>Posted on:</b> {{posts.date | date:"H:i D, d M"}}</span>
</form>
</div>
<br>
{% endfor %}
I think that the server is not able to fetch the value from the input box. I don't know what have i done wrong. Please help me. Thanku
Looks like in the line
post_connected = post.objects.get(id=postid)
That you have a model named post. Make sure the model has been imported to views.py and the model has been migrated before running the code.
If that is done, make sure the .get() is being used to call an entry that exists. According to the documentation,
If there are no results that match the query, get() will raise a
DoesNotExist exception. This exception is an attribute of the model
class that the query is being performed on.
There were many input boxes with the same id as the input box was inside the for loop. Hence, it was getting multiple values and nothing was happening.
I have been trying to add friend system in which the user can add and remove friends (other users), after finishing with the code, I found an error that when the logedin user tries to add a friend from other user's profile, the add friend button redirects to the logedin user profile making it imposible to add a new friend, it can just add himself as a friend. I personally think the error is on the views.py profile view.
views.py (profile shows user's profile and change_friend is the one that adds and removes frinds)
def profile(request, username=None):
friend = Friend.objects.filter(current_user=request.user).first()
friends = []
if friend:
friends = friend.users.all()
if username:
post_owner = get_object_or_404(User, username=username)
user_posts=Post.objects.filter(user_id=post_owner)
else:
post_owner = request.user
user_posts=Post.objects.filter(user=request.user)
args1 = {
'post_owner': post_owner,
'user_posts': user_posts,
'friends': friends,
}
return render(request, 'profile.html', args1)
def change_friends(request, operation, pk):
friend = User.objects.get(pk=pk)
if operation == 'add':
Friend.make_friend(request.user, friend)
elif operation == 'remove':
Friend.lose_friend(request.user, friend)
return redirect('profile')
models.py
class Friend(models.Model):
users = models.ManyToManyField(User, default='users', blank=True, related_name='users')
current_user = models.ForeignKey(User, related_name='owner', on_delete=models.CASCADE, null=True)
#classmethod
def make_friend(cls, current_user, new_friend):
friend, created = cls.objects.get_or_create(
current_user=current_user
)
friend.users.add(new_friend)
#classmethod
def lose_friend(cls, current_user, new_friend):
friend, created = cls.objects.get_or_create(
current_user=current_user
)
friend.users.remove(new_friend)
profile.html
<div class="media">
<div class="media-body">
<h2 class="account-heading">{{ post_owner.username }}</h2>
<p class="text-secondary">{{ post_owner.email }}</p>
{% if not user in friends %}
<a href="{% url 'change_friends' operation='add' pk=user.pk %}">
<button type="button">add Friend</button>
</a>
{% endif %}
</div>
</div>
<div>
<h2>Friends</h2>
{% for friend in friends %}
<p>{{ friend.username }}</p>
<a href="{% url 'change_friends' operation='remove' pk=friend.pk %}">
<button type="button">Remove Friend</button>
</a>
{% endfor %}
</div>
urls.py
urlpatterns = [
path('profile/<str:username>/', views.profile, name='profile_pk'),
url(r'^connect/(?P<operation>.+)/(?P<pk>\d+)/$', views.change_friends, name='change_friends'),
]
The problem is that you're passing the request.user object to the change_friends view, through the user object. By default user == request.user when used in a template.
Just change that line that you have in profile.html with:
<a href="{% url 'change_friends' operation='add' pk=post_owner.pk %}">
<button type="button">add Friend</button>
</a>
Now, I note that you're redirecting the user to the profile view once they add a new friend, and that's not what you want. That's happening because when you're calling the redirect() function in your change_friends view, you are not passing any parameter to the profile view. You defined the username should be None by default and then you're saying that if not username then post_owner should be request.user
How to change this? Well, just pass the desired username when calling redirect as a keyword argument. Something as follows:
return redirect('profile', username=friend.username)
In your view, friends address to already added friends, you want to get the users are eligible to add as friend to request.user object. To achieve this,
In your profile view:
def profile(request, username=None):
friend = Friend.objects.filter(current_user=request.user).first()
friends = []
# In case of friend is None, I send all users except request user, to be able to add on html template.
friends_to_add = User.objects.exclude(id=request.user.id)
if friend:
friends = friend.users.all()
# here we have a valid friend (who is the user request has)
# so I don't want to send users to template who are already friend of request user.
friends_to_add = friends_to_add.exclude(id__in=friend.users.values_list("id"))
if username:
post_owner = get_object_or_404(User, username=username)
user_posts=Post.objects.filter(user_id=post_owner)
else:
post_owner = request.user
user_posts=Post.objects.filter(user=request.user)
args1 = {
'post_owner': post_owner,
'user_posts': user_posts,
'friends': friends,
'friends_to_add': friends_to_add,
}
return render(request, 'profile.html', args1)
In your template file, you can use them as:
<div class="media">
<div class="media-body">
<h2 class="account-heading">{{ post_owner.username }}</h2>
<p class="text-secondary">{{ post_owner.email }}</p>
{% for user in friends_to_add %}
<a href="{% url 'change_friends' operation='add' pk=user.pk %}">
<button type="button">add Friend</button>
</a>
{% endfor %}
</div>
</div>
I hope this will make sense to you. You can ask me anything you cannot understand above from comments if you need.
Okay so this is first time using pagination with Django and I am trying to prevent it from reloading my view on each page turn.
I'm handling the pagination in the view like this:
page = request.GET.get('page', 1)
print page
paginator = Paginator(list(od.iteritems())[:24], 12)
try:
data = paginator.page(page)
except PageNotAnInteger:
data = paginator.page(1)
except EmptyPage:
data = paginator.page(paginator.num_pages)
print data
save_query_form = SaveQueryForm(request.POST or None)
#if request.method == 'POST':
if save_query_form.is_valid():
profile = save_query_form.save(commit=False)
profile.user = request.user
profile.save()
context = {
"title":"Search",
'data': data,#list(od.iteritems()),
'tools': od_tools.iteritems(),
'methods': od_methods.iteritems(),
'data4': od_data.iteritems(),
'search_phrase': " ".join(instanceValuesString),
'json_dump': js_data,
'form': save_query_form,
}
return render(request, 'results.html', context)
and the pagination is handled in the html:
{% if data.has_other_pages %}
<div id='page-slide'>
<ul class="pagination" start='$offset'>
{% if data.has_previous %}
<li>«</li>
{% else %}
<li class="disabled"><span>«</span></li>
{% endif %}
{% for i in data.paginator.page_range %}
{% if data.number == i %}
<li class="active"><span>{{ i }} <span class="sr-only">(current)</span></span></li>
{% else %}
<li>{{ i }}</li>
{% endif %}
{% endfor %}
{% if data.has_next %}
<li>»</li>
{% else %}
<li class="disabled"><span>»</span></li>
{% endif %}
</ul>
</div>
{% endif %}
The issue that I am having is that whenever I switch to another page my entire view will run again and the data will does not reflect the original search query and instead defaults to an empty query.
I was wondering if there is a simple way to either handle pagination dynamically or prevent the page reload when toggling between pages?
Any help is appreciated, thanks.
Update Search Form:
<form action="{% url 'results-view' %}" method="POST" class="autocomplete-me ui-widget" id="myform" >
{% csrf_token %}
<div class="ui-widget" style="text-align:center;">
<input type="text" id="id_q" name="q" placeholder="{{ search_phrase }}">
<br></br>
<div style="text-align:center;" id='adjust-button'>
<input type='submit' class='btn btn-secondary btn-lg' id ='search-btn' value='Search'/>
<a class='btn btn-secondary btn-lg' id ='clear-btn' href="{% url 'inital' %}">Clear</a>
</div>
</div>
</form>
You noted in a comment that you get your search value with instanceValuesString = request.POST.get(u"q").encode('utf-8').strip(). As one commenter correctly pointed out, this means that when you click your "next page" links (making a GET request), your view doesn't receive the information it needs to return search results.
One way to fix this would be to get your instanceValuesString from a GET request instead of a POST request. For instance, perhaps your list view is at
http://example.com/StuffList
You could look for URLs that provide a search querystring:
http://example.com/StuffList?search=goodstuff
And then grab that in your view:
instanceValuesString = request.GET.get('search', None)
if instanceValuesString is not None:
#you have detected a search query; filter results, process request, etc.
One side effect here is that the way you currently construct your next/previous page URLs will break. Consider the example search URL; your current template would construct a link for page 2 like so:
http://example.com/StuffList?search=goodstuff?page=2
This won't work; it should be &page=2. Fortunately there's an easy fix; check out the second answer to this question: Altering one query parameter in a url (Django). Using that url_replace instead of constructing those links with the basic url template tag will solve this part of the issue.
This is very much simplified with below package
http://django-simple-pagination.readthedocs.io/en/latest/