Accessing a ManyToManyField's contents for a Django template - python

I'm making a low-spec e-bay clone, I'm trying to implement a watchlist function but the problem is I can't access the ManyToManyField in the Watchlist model so I can use the contents of the field in the template I'm using. To display each user's watchlist to them, so far I only get this result:
but I need to get a result like so:
Code I'm using:
watchlist.html
{% extends "auctions/layout.html" %}
{% load static %}
{% block title %} {{name}}'s Watchlist {% endblock %}
{% block body %}
<h2>{{name}}'s Watchlist</h2>
{% for listing in watchlist %}
<div class='listing'>
<h3>{{ listing.list_title}}</h3>
{% if listing.img_url == "" %}
<a href='#'><img src="{% static 'auctions/img404.png' %}" class='img-fluid'></a>
{% else %}
<a href='#'><img src="{{ listing.img_url }}" class="img-fluid" alt='image of {{ listing.list_title }}'></a>
{% endif %}
<p>
{{ listing.desc }}
</p>
<p>
Current Bid: ${{ listing.start_bid }}
</p>
{% if listing.category == "" %}
<p>Category: No Category Listed</p>
{% else %}
<p>Category: {{ listing.category }}</p>
{% endif %}
<a href='#' class='btn btn-primary' id='go'>Go To Listing</a>
</div>
{% endfor %}
{% endblock %}
views.py
def render_listing(request, title):
if request.method == "POST":
form = BidForm(request.POST)
bid = int(request.POST['new_bid'])
listing = Auction_Listing.objects.get(list_title=title)
comments = Auction_Comments.objects.all().filter(auction_id=listing)
if bid < listing.start_bid:
error = True
else:
error = False
listing.start_bid = bid
listing.save()
return render(request, 'auctions/listing.html', {
"listing": listing,
"form": form,
"comments": comments,
"error": error
})
else:
form = BidForm()
listing = Auction_Listing.objects.get(list_title=title)
comments = Auction_Comments.objects.all().filter(auction_id=listing)
return render(request, 'auctions/listing.html', {
"listing": listing,
"form": form,
"comments": comments,
"error": False
})
#login_required
def watchlist_render(request):
user = request.user.id
username = request.user.username
watched = Watchlist.objects.filter(user_id=user)
return render(request, 'auctions/watchlist.html', {
"watchlist": watched,
"name": username
})
models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.db.models.deletion import CASCADE
CATEGORIES = [
('Appliances', 'Appliances'),
('Tech', 'Tech'),
('Gaming', 'Gaming'),
('Fashion', 'Fashion'),
('Sports and Fitness','Sports and Fitness'),
('Other','Other'),
("Hygiene and Medicine","Hygiene and Medicine"),
("Stationery","Stationery"),
('Decor', 'Decor'),
('Furniture','Furniture'),
('Cars and Mechanical Things','Cars and Mechanical Things'),
("Tools","Tools")
]
# Create models here
class User(AbstractUser):
pass
class Auction_Listing(models.Model):
user_id = models.IntegerField(default=1)
list_title = models.CharField(max_length=64)
desc = models.TextField(max_length=324)
img_url = models.URLField(max_length=200, null=True, blank=True)
start_bid = models.IntegerField()
category = models.CharField(choices=CATEGORIES, max_length=35, null=True, blank=True)
def __str__(self):
return f"ID:{self.id}, {self.list_title}: {self.desc}, {self.start_bid} posted by user:{self.user_id} in Category:{self.category}, url:{self.img_url}"
class Bids(models.Model):
bid = models.IntegerField(default=0)
user_id = models.IntegerField(default=1)
auction_id = models.ManyToManyField(Auction_Listing)
def __str__(self):
return f"ID:{self.id}, Bid {self.bid} posted by user:{self.user_id} on auction {self.auction_id}"
class Auction_Comments(models.Model):
user_id = models.IntegerField(default=1)
comment = models.TextField(max_length=324, default='N/A')
auction_id = models.ManyToManyField(Auction_Listing)
def __str__(self):
return f"ID:{self.id}, Comment: {self.comment} posted by user:{self.user_id} on auction {self.auction_id}"
class Watchlist(models.Model):
user_id = models.ForeignKey(User, on_delete=CASCADE,default=1)
auction_id = models.ManyToManyField(Auction_Listing, related_name='auction_listing')
def __str__(self):
return f"ID:{self.user_id} on auction {self.auction_id}"
Thanks in advance for the help!
Kind Regards
PrimeBeat
EDIT: The results of the links as #nigel222 requested

Ahh CS50W. I love this course. Anyway, I recognize where you've gotten stuck. What you're doing is that you're passing the Watchlist object in the context, instead of passing a list of listings.
This is why your {% for listing in watchlist %} is failing, because watchlist is a single object.
What you can do instead is, in your views.py, get the full list of watched items, and pass in that list in as the context. So:
#login_required
def watchlist_render(request):
user = request.user.id
username = request.user.username
# This will give you the Watchlist instance
watchlist = Watchlist.objects.filter(user_id=user)
# I would recommend renaming auction_id (in your models.py file) to `auctionItems` or 'listings'
# because it returns an object of type Auction_Listing, rather than just the id.
# for eg. watchlist.listings.all() allows you to instinctively understand what you're trying to do.
# Now you can extract the listings from the watchlist
listings = watchlist.auction_id.all()
return render(request, 'auctions/watchlist.html', {
"watchlist": listings,
"name": username
})
Now in your template you can access each listing in the list: {% for listing in listings %}

Related

Check if logged in user is the author of a post | Django | If-else doesn't work

I want to check if the logged in user is the author of a post in my Forum. I have written some code to figure that out:
<div class="right-section-posts">
user: {{ user }} <!--Output: Admin-->
author: {{ post.author }} <!--Output: Admin-->
{% if user == post.author %}
<form action="DELETE">
{% csrf_token %}
<button type="submit" class="delete-btn" name="post-id" value="{{ post.id }}">Delete</button>
</form>
<button class="edit-btn">Edit</button>
{% endif %}
</div>
They both output the same but the statement returns false! Why?
Models.py
class Post(models.Model):
vote_count = models.IntegerField(default=0)
id = models.BigAutoField(primary_key=True)
created_at = models.DateField(default=date.today)
title = models.CharField(max_length=100)
description = models.CharField(max_length=1000)
tags = models.CharField(max_length=200)
author = models.CharField(max_length=100, default="none")
def __str__(self):
return str(self.id) + ' ' + self.title
I tried different ways to get the Current user and the author. Doesn't work to.
(I think you might say that I should use ForeignKey instead of ´CharField´, when using that I get this Error:
ERROR: Column forum_app_post.author_id does not exist.
LINE 1: ...app_post". "description", "forum_app_post". "tags", "forum_app...
^
HINT: Perhaps the intention was to refer to the "forum_app_post.author" column.
)
The author field cannot be a CharField because it represents the user. You need to set author field as foreignkey.
You need to update your model like this:
from django.contrib.auth.models import User
class Post(models.Model):
vote_count = models.IntegerField(default=0)
id = models.BigAutoField(primary_key=True)
created_at = models.DateField(default=date.today)
title = models.CharField(max_length=100)
description = models.CharField(max_length=1000)
tags = models.CharField(max_length=200)
author = models.ForeignKey(User,on_delete= models.CASCADE, verbose_name='Post Author')
def __str__(self):
return str(self.id) + ' ' + self.title
If you want to check the logged in user from all the registered posts, you should get all the posts first.
def get_all_posts(request):
posts = Post.objects.filter.all()
context = {
"posts" : posts,
}
return render(request,"my_page.html",context)
Then in the html page :
<div class="right-section-posts">
{% if posts %}
{% for post in posts %}
{% if request.user == post.author %}
<!--do what you want here-->
{% else %}
{% endif %}
{% endfor %}
{% else %}
<div class="alert alert-info">You have no registered post yet!</div>
<!-- /.container-fluid -->
</div>
{% endif %}
</div>
I also recommend using django-taggit for tags.

How do I iterate inside a queryset in django template

My django view returns a dictionary people with values for all keys in list format.
The code for the view is:
class ProjectDetail(View):
def get(self, request, pk, *args, **kwargs):
project = Project.objects.get(pk=pk)
roles = Role.objects.filter(project=pk)
people = {}
for role in roles:
try:
people[role] += Person.objects.filter(role=role.pk)
except KeyError:
people[role] = [Person.objects.filter(role=role.pk)]
context = {
'project': project,
'people': people
}
return render(request, 'project_management/project_detail.html', context)
My Models
class Project(models.Model):
title = models.CharField(max_length=2000)
introduction = models.TextField(blank=True)
class Role(models.Model):
role_name = models.CharField(max_length=30)
project = models.ForeignKey(Status, on_delete=models.CASCADE)
class Person(models.Model):
name = models.CharField(max_length=30, blank=True, null=True)
role = models.ForeignKey(Role, on_delete=models.CASCADE)
In order to iterate through the dictionary I used:
{% for designation, persons in people.items %}
<h5> {{ designation.role_name }} </h5>
<ul>
{% for person in persons %} <!-- My Problem lies here, this loop is not iterating, it's running only once-->
<li> {{person}} </li>
{% endfor %}
</ul>
{% endfor %}
The result I got is:
I want the items inside queryset listed out separately, instead of being shown inside square brackets. How can I make that happen?
You don't need to do all this work. You can pass only the project. For example with:
from django.shortcuts import get_object_or_404
class ProjectDetail(View):
def get(self, request, pk, *args, **kwargs):
project = get_object_or_404(Project, pk=pk)
return render(request, 'project_management/project_detail.html', {'project': project})
or even simpler with a DetailView [Django-doc]:
class ProjectDetail(DetailView):
queryset = Status.objects.prefetch_related(
'role_set', 'role_set__person_set'
)
template_name = 'project_management/project_detail.html'
context_object_name = 'project'
Then in the template you can render this with:
{% for role in project.role_set.all %}
<h5> {{ role.role_name }} </h5>
<ul>
{% for person in role.person_set.all %}
<li> {{ person.name }} </li>
{% endfor %}
</ul>
{% endfor %}

Comment form in django didn't save the new comments

I've created a new form for comments the articles on a website. When I add the new comment from django admin everything works ok, but when I try to add the new comment directly from detail page nothings happen and I'am redirecting to the page with list of articles.
here are my files
models.py:
class Komentarz(models.Model):
post = models.ForeignKey(Wpisy, related_name="komentarze", verbose_name="Komentarze do artykułu", on_delete=models.CASCADE)
name = models.CharField(max_length=80, verbose_name="Imię")
email = models.EmailField(verbose_name="Email")
content = models.TextField(verbose_name="Treść komentarza")
created_date = models.DateTimeField(verbose_name="Utworzono", auto_now_add=True)
active = models.BooleanField(verbose_name="Aktywny?", default=True)
class Meta:
ordering = ('created_date',)
verbose_name="Komentarz"
verbose_name_plural="Komentarze"
def __str__(self):
return 'Komentarz dodany przez {} dla strony {}'.format(self.name, self.post)
vies.py with the function of details
from django.shortcuts import render, get_object_or_404
from .models import Wpisy, Komentarz
from .forms import KomentarzeForma
....
def detale_bajki(request, slug, ):
detale_bajki = get_object_or_404(Wpisy, slug=slug)
komentarze = detale_bajki.komentarze.filter(active=True)
if request.method == 'POST':
formularz_komentarza = KomentarzeForma(data=request.POST)
if formularz_komentarza.is_valid():
nowy_komentarz = formularz_komentarza.save(commit=False)
nowy_komentarz.detale_bajki = detale_bajki
nowy_komentarz.save()
else:
formularz_komentarza = KomentarzeForma()
return render(request, 'bajki/detale_bajki.html', {'detale_bajki': detale_bajki, 'komentarze': komentarze, 'formularz_komentarza': formularz_komentarza})
forms.py
from .models import Komentarz
from django import forms
class KomentarzeForma(forms.ModelForm):
class Meta:
model = Komentarz
fields = ('name', 'email', 'content')
and detail.html
...
{% with komentarze.count as total_komentarze %}
<h2>
{{ total_komentarze }} komentarz{{ total_komentarze|pluralize:"y" }}
</h2>
{% endwith %}
{% for komentarz in komentarze %}
Komentarz dodany przez <strong>{{komentarz.name}}</strong>
{{komentarz.created_date}}
<p>
{{ komentarz.content|linebreaks }}<br>
{% empty %}
<p>Nie dodano jeszcze żadnych komentarzy</p>
{% endfor %}
{% if nowy_komentarz %}
<h2>Twój komentarz został dodany</h2>
{% else %}
<h2>Dodaj nowy komentarz</h2>
<form action="." method="post">
{{formularz_komentarza.as_p}}
{% csrf_token %}
<p><input type="submit" value="Dodaj komentarz"></p>
</form>
{% endif %}
clas Wpisy in models.py
class Wpisy(models.Model):
title = models.CharField(max_length=400, verbose_name="Tytuł")
slug = models.SlugField(unique=True, max_length=400,verbose_name="Przyjazny adres url")
content = models.TextField()
status_audio = models.BooleanField(default=False, verbose_name="Czy dostępny będzie plik audio?")
audio_file = models.FileField(upload_to='uploads/',verbose_name="Plik audio")
created_date = models.DateTimeField(blank=True, null=True, verbose_name="Data utworzenia")
category = models.ForeignKey(Kategorie, verbose_name="Kategoria", on_delete=models.CASCADE)
class Meta:
verbose_name="Wpis"
verbose_name_plural="Wpisy"
def __str__(self):
return self.title
Your url pattern is
path('bajki/<slug>', views.detale_bajki, name='detale_bajki')
Note that it doesn't have a trailing slash. Your form's action is "."
<form action="." method="post">
That means you are submitting to /bajki/, which is the wrong URL.
You could fix this by adding a trailing slash to the url (which is common in Django URLs)
path('bajki/<slug>/', views.detale_bajki, name='detale_bajki')
Or you could change the form action to "" instead of ".". In the comments it looks like you fixed the issue by changing the form action to {{ detale_bajki.slug }}.
However these changes to the form action are fragile, and could break if you change your URL patterns again. The best approach is to use the {% url %} tag to reverse the correct URL.
<form action="{% url 'detale_bajki' detale_bajki.slug %}" method="post">
Try out:
nowy_komentarz.post = detale_bajki
...
return render(request, 'html page', {'key': 'what you want to return to your context'}) # don't fornget to add some return to your view.

Django editing model instance

The Django project that I am working on lists patient details and lets the user edit the details. I have been able to list it out but views.py is not getting linked to the url for updating the list.
views.py:
def update_patient(request, patient_id):
patient = Patient.objects.get(id=patient_id)
if request.method != 'POST':
form = PatientForm(instance=patient)
else:
# POST data submitted; process data.
form = PatientForm(instance=patient, data=request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('patient:patient',
args=[patient.id]))
context = { 'patient': patient, 'form': form}
return render(request, 'patient/update_patient.html', context)
models.py:
class Patient(models.Model):
patientID = models.CharField(max_length=20)
firstName =models.CharField(max_length=20)
lastName = models.CharField(max_length=20)
age = models.IntegerField(max_length=None)
SSN = models.CharField(max_length=15)
address = models.CharField(max_length=200)
date_added = models.DateTimeField(auto_now_add=True)
urls.py:
url(r'^patients/(?P<patient_id>\update\d+)/$', views.update_patient, name='update'),
update_patient.html:
{% extends "patient/base.html" %} {% block content %}
<p>{{ patient }}
</p>
<p>Update Patient:</p>
<form action="{% url 'patient:update' patient.id %}" method='post'>
{% csrf_token %} {{ form.as_p }}
<button name='submit'>add entry</button>
</form>
{% endblock content %}
Your URL pattern is wrong, you have \update within capturing group, it shouldn't be, change the pattern to this:
url(r'^patients/(?P<patient_id>\d+)/update/$', views.update_patient, name='update')

Unable to render ForeignKey fields in my template

I'm making a comment system for my django app and i've been told it's best to make a seperate model for comment-voting. So i've done that and here's my models.py:
class Comment(models.Model):
user = models.ForeignKey(User, default=1)
destination = models.CharField(default='1', max_length=12, blank=True)
author = models.CharField(max_length=120, blank=True)
comment_id = models.IntegerField(default=1)
parent_id = models.IntegerField(default=0)
comment_text = models.TextField(max_length=350, blank=True)
timestamp = models.DateTimeField(default=timezone.now, blank=True)
def __str__(self):
return self.comment_text
class CommentScore(models.Model):
user = models.ForeignKey(User, default=1)
comment = models.ForeignKey(Comment, related_name='score')
upvotes = models.IntegerField(default=0)
downvotes = models.IntegerField(default=0)
def __str__(self):
return str(self.comment)
Here's my views.py where the comments are created:
def article(request, category, id):
name = resolve(request.path).kwargs['category']
for a, b in CATEGORY_CHOICES:
if b == name:
name = a
instance = get_object_or_404(Post, id=id, category=name)
allauth_login = LoginForm(request.POST or None)
allauth_signup = SignupForm(request.POST or None)
#comments
comment = CommentForm(request.POST or None)
ajax_comment = request.POST.get('text')
comment_length = len(str(ajax_comment))
comment_list = Comment.objects.filter(destination=id)
score = CommentScore.objects.filter(comment=comment_list)
if request.is_ajax():
if comment.is_valid():
comment = Comment.objects.create(comment_text=ajax_comment, author=str(request.user), destination=id)
print(comment)
comment.save()
score = CommentScore.objects.create(comment=comment)
score.save()
username = str(request.user)
return JsonResponse({'text': ajax_comment, 'text_length': comment_length, 'username': username})
else:
print(comment.errors)
context = {
'score': score,
'comment_list': comment_list,
'comment': comment,
'instance': instance,
'allauth_login': allauth_login,
'allauth_signup': allauth_signup
}
return render(request, 'article.html', context)
So the comment works fine, but as you can see a couple lines later i'm trying to then create a CommentScore instance to match with the comment. In my template, I've rendered each comment and it's fields (comment_text, author etc), but I want to render the upvotes field associated with that comment. How would I do this?
template
{% for i in comment_list %}
<div class='comment_div'>
<h3>{{ i.author }}</h3>
<p>{{ i.comment_text }}</p><br>
</div>
{% endfor %}
forms.py
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = [
'comment_text',
'id',
'author',
'destination',
]
I've already tried the following and they haven't worked;
{% for i in comment_list %}
<div class='comment_div'>
<h3>{{ i.author }}</h3>
<p>{{ i.comment_text }}</p><br>
{% for i in comment_list.score_set.all %}
{{ i.upvotes }} #renders nothing
{% endfor %}
</div>
{% endfor %}
{% for i in comment_list %}
<div class='comment_div'>
<h3>{{ i.author }}</h3>
<p>{{ i.comment_text }}</p><br>
{% for j in i.score %}
{{ j.upvotes }} #Error: 'RelatedManager' object is not iterable
{% endfor %}
</div>
{% endfor %}
Having a lot of trouble so help is appreciated.
Changing "i.score" to "i.score.all" resolves the problem as the RelatedManaager error usually happens when you are trying to iterate over the manager and not the objects selected by that manager. - Solved by #joe-j
So it works now but if someone could explain the 2nd line of this syntax that would be great:
comment_list = Comment.objects.filter(destination=id)
score = CommentScore.objects.filter(comment=comment_list)
What exactly is happening when I assign comment=comment_list here? I copied this code from someone else but i'm still abit unsure how it's working.

Categories