I'm building an instagram-ish clone in Django. I have the basic functionality working, where a user can post an image, and this is displayed on the main page. I would like to make a 'user page' that only displays posts from a user. For example, example.com/foobar would only display posts from the user 'foobar'.
I believe i have the urls.py and template working correctly, but I can not figure out how to only iterate through items and pull out ones of a specific user. I realize this should be a queryset of some kind, but other than that I'm stumped. Should this be its own class, or could I extend the existing PostList class to pull out a single author's posts?
post_detail.html - gets all the images stored in the database, this works fine.
{% for post in object_list %}
<td><img src="{{ post.image.url }}" width="300"></td>
{% if forloop.counter|modulo:4 %}
</tr><tr>
{% endif %}
{% endfor %}
profile.html - shows all posts from a user (as in example.com/foobar)
<table>
<tr>
{% for post in object_list %}
<td><img src="{{ post.image.url }}" width="300"></td>
{% if forloop.counter|modulo:4 %}
</tr><tr>
{% endif %}
{% endfor %}
</tr>
</table>
urls.py - I believe this works correctly.
urlpatterns = [
path('admin/', admin.site.urls),
path('', PostList.as_view(), name='list'),
path('<str:username>/', Profile.as_view(), name='user_profile'),
views.py:
from posts.models import Post
class PostList(ListView):
ordering = ['-created']
paginate_by = 12
model = Post
class Profile(ListView):
template_name = 'posts/profile.html'
UserName = self.kwargs.get("username")
queryset = PostList.queryset
.filter(author = UserName)
return queryset
models.py:
class Post(models.Model):
image = models.ImageField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
You can override get_queryset():
class ProfileView(ListView):
template_name = 'posts/profile.html'
model = Post
def get_queryset(self, **kwargs):
return super().get_queryset(**kwargs).filter(
author__username=self.kwargs['username']
)
Note: In Django, class-based views (CBV) often have a …View suffix, to avoid a clash with the model names.
Therefore you might consider renaming the view class to ProfileView, instead of Profile.
Related
I am writing a simple app to track sales leads based on the Django tutorial "Writing your first Django app" (https://docs.djangoproject.com/en/4.0/intro/tutorial01/). Everything works well except for displaying a ListView of the existing leads.
Here are the relevant python snippets:
Leads/models.py
class Leads(models.Model):
id = models.IntegerField(primary_key=True)
last = models.CharField(max_length=32)
first = models.CharField(max_length=32)
config/urls.py
urlpatterns = [
# Django admin
path('admin/', admin.site.urls),
# User management
path('accounts/', include('allauth.urls')),
# Local apps
path('leads/', include('leads.urls'))
leads/urls.py
app_name = 'leads'
urlpatterns = [
path('', LeadsListView.as_view(), name='list'),
path('<int:pk>/', LeadsDetailView.as_view(), name='detail'),
]
leads/views.py
class LeadsListView(ListView):
model = Leads
template_name = 'leads/list.html'
context_object_name = 'leads_list'
def get_queryset(self):
return Leads.objects.order_by('id')
class LeadsDetailView(DetailView):
model = Leads
template_name = 'leads/detail.html'
The link in the 'home.html' template, which displays a menu option correctly:
Leads
And finally, the 'list.html' template that does not display at all. Instead of showing the list of leads, it remains on the home page.
{% block content %}
{% for lead in lead_list %}
<div class="lead-entry">
<h2>{{ lead.fname }} {{ lead.lname }}</h2>
</div>
{% endfor %}
{% endblock content %}
I'm missing something fundamental here....
There are some minor mistakes such as:
Issue
The context_object_name='leads_list, and you have defined in your template file as lead_list, missed the s.
Your Model field's name is first and last, not fname and lname.
Solution:
Try this out in your template:
{% block content %}
{% for lead in leads_list %}
<div class="lead-entry">
<h2>{{ lead.first }} {{ lead.last }}</h2>
</div>
{% endfor %}
{% endblock content %}
I have changed lead.fname to lead.first and lead.lname to lead.last and also add s to lead_list, it is now leads_list.
Note: Models in django doesn't require its name in plural form, it would be better if you only give name model name Lead rather than Leads, as django itself adds s as the suffix.
Note: If you are overriding the default AutoField generated by django which is id, so you must override it with AutoField[django-doc] rather than making IntegerField primary key.
I'm learning Django and I am currently building blog app. I have a problem. I built functionalities to add new posts and to comment to new posts. Now I struggle with creating the possibility to delete comments by users.
I assigned an id (primary key) of given comment to button "delete" which is present in all comments fields in purpose to know which comment I want to delete. But now I don't know how to access to this HTML elements in backend and fetch this id's.
Part of my html file:
{% for comment in comments %}
{% if comment.post == object %}
<div class="article-metadata">
<small>{{ comment.add_date|date:"M/d/Y" }} <b>{{ comment.author }}</b></small>
{% if comment.author == user %}
Delete
{% endif %}
<p>{{ comment.comm_content }}</p>
</div>
{% endif %}
{% endfor %}
My class based view in views.py where I want to touch this id:
class PostDetailView(DetailView):
model = Post
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['comments'] = Comment.objects.all()[::-1]
return context
# In this function I want to access my comment id and remove this comment from database
def delete_comment(self):
post_to_delete = Post.objects.get(id=my_comment_id)
del post_to_delete
I know this can be solved somehow using jquery but I don't know javascript and for now I would like to know how to do it using python only. Thanks for any hints.
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})
First, add a URL pattern like:
# urls.py
path('comments/delete/<int:pk>/', views.CommentDeleteView.as_view(), name='delete_comment'),
next, you need a view to handle the logic:
# views.py
from django.urls import reverse_lazy
class CommentDeleteView(DeleteView):
model = Comment
def get_success_url(self):
return reverse_lazy('post-detail', kwargs={'pk': self.object.post.pk})
# if you don't want to send POST request, you can use:
def get(self, request, *args, **kwargs):
return self.post(request, *args, **kwargs)
Django's generic DeleteView will delete the object only if you send a POST request to it, so we will call the post method in get, to make it work with a GET request; Although, generally speaking, it's not a good idea; you should add a confirmation form (like a modal) and send a POST from there.
and finally, for your template:
{% for comment in comments %}
{% if comment.post == object %}
<div class="article-metadata">
<small>{{ comment.add_date|date:"M/d/Y" }} <b>{{ comment.author }}</b></small>
{% if comment.author == user %}
Delete
{% endif %}
<p>{{ comment.comm_content }}</p>
</div>
{% endif %}
{% endfor %}
I want to add data through form and see it queried in my template
blog url:
urlpatterns = [
url(r'^admin/',admin.site.urls),
url(r'^blog/',include('content.urls',)),
]
content url:
urlpatterns = [
url(r'^add/$', views.add_content, name='content'),
]
models.py
from django.db import models
class AddContent(models.Model):
content_name = models.CharField(max_length=100, default='', blank=False, unique=True)
def __str__(self):
return self.content_name
forms.py
from django import forms
from .models import AddContent
class AddContentForm(forms.ModelForm):
class Meta:
model = AddContentModel
fields = [
"content_name",
]
views.py
def add_content(request):
form = AddContentForm(request.POST or None)
if form.is_valid():
instance = form.save(commit=False)
instance.save()
return redirect("/blog/content/")
content_data = AddContent.objects.all()
context = {
"form":form,
"content":content_data,
}
return render(request, "add_content.html", context)
def view_content(request):
view_data = AddContent.objects.all()
context = {
"show_content":view_data,
}
return render(request, 'show_content.html', context)
templates:
add_content.html
{% extends 'base.html' %}
{% block content %}
<form method='POST' action="/blog/content/">
{% csrf_token %}
{{ form.as_p }}
<input type='submit' value='Add Content'/>
<h3>Recent content</h3>
{% for show in show_content %}
<p>{{ show.content_name }}</p>
{% endfor %}
{% endblock %}
The form data is not being saved, when I add it through admin interface it gives the result but form fails.
(this is just some useless content that I am writing in the bracket, stackoverflow didn't allow me posting as it looks like my post is mostly code; please add more details it said but i think the code has lot of details and i cant write anything just for the sake of length)
Your model form should look like this
from django import forms
from .models import AddContent
class AddContentForm(forms.ModelForm):
class Meta:
model = AddContent
fields = [
"content_name",
]
You've defined the wrong model name in the Meta class of model form. It should be AddContent not AddContentModel
I'm new to django too, but don't you need to specify the request inside the functions in views.py like :
def add_content(request):
if(request.method == 'POST'):
# rest of code here
Your form does not post to the view that saves the data; it posts directly to the view_content view, which ignores the data completely.
You can set the form action to "." to get it to post back to the same view that rendered it.
(As an additional point, you should avoid hard-coding URLs. Use the {% url %} tag in templates, and the reverse() function in views, to output URLs based on view names.)
First :
Correct the redirect() in your form.
return redirect("add_content", request.POST= None)
Second : you need to specify a URL for the content view : view_content
url(r'^content/$', views.view_content, name='content'),
And You need a template to render content (show_content.html :
{% extends 'base.html' %}
{% block content %}
<h3>Recent content</h3>
{% for show in show_content %}
<p>{{ show.content_name }}</p>
{% endfor %}
{% endblock %}
Edit :
Your form is not well defined. I edit my answer to make it complete :
You have to correct the model in your form (AddContent not AddContentModel) :
class Meta:
model = **AddContent**
fields = [
"content_name",
]
UPDATE #4
Issue: I've been having trouble have content appear on my detailed page view, specifically, this content is in two separate html files called slider.html and sidebar.html, which are being pulled in using {% include %} in Django.
Status: Issue still unresolved, last update on Thurs. Dec 18, 11 a.m.
I've made changes to my models.py and my detailed.html
Yes, the detailed.html page does have empty divs with classes on them
I've included my views.py in the main post.
views.py
from django.views import generic
from . import models
from .models import FullArticle
# Create your views here.
class BlogIndex(generic.ListView):
queryset = models.FullArticle.objects.published()
template_name = "list.html"
randomArticle = FullArticle.objects.order_by('?').first()
class BlogDetail(generic.DetailView):
model = models.FullArticle
template_name = "detailed.html"
models.py
from django.db import models
from django.core.urlresolvers import reverse
# Create your models here.
class FullArticleQuerySet(models.QuerySet):
def published(self):
return self.filter(publish=True)
class FullArticle(models.Model):
title = models.CharField(max_length=150)
author = models.CharField(max_length=150)
slug = models.SlugField(max_length=200, unique=True)
pubDate = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
category = models.CharField(max_length=150)
heroImage = models.CharField(max_length=250, blank=True)
relatedImage = models.CharField(max_length=250, blank=True)
body = models.TextField()
publish = models.BooleanField(default=True)
gameRank = models.CharField(max_length=150, blank=True, null=True)
objects = FullArticleQuerySet.as_manager()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("FullArticle_detailed", kwargs={"slug": self.slug})
def get_context_data(self, **kwargs):
context = super(BlogDetail, self).get_context_data(**kwargs)
context['object_list'] = models.FullArticle.objects.published()
return context
class Meta:
verbose_name = "Blog entry"
verbose_name_plural = "Blog Entries"
ordering = ["-pubDate"]
detailed.html
<!-- This grabs the sidebar snippet -->
{% include "sidebar.html" with object_list=object_list %}
<div class="mainContent clearfix">
<h1>{{object.title}}</h1>
<p class="date">{{object.pubDate|date:"l, F j, Y" }}</p> | <p class="author">{{object.author}}</p>
<img src="{{object.heroImage}}" alt="" class="largeImage">
<div class="contentBlock">
<img src="{{object.relatedImage}}" alt="" class="relatedImage">
<p class="content">{{object.body|linebreaks}}</p>
</div><!-- /.contentBlock -->
<!-- This grabs the slider snippet -->
{% include "slider.html" with object_list=object_list%}
</div><!-- /.mainContent -->
sidebar.html
<div class="navContent">
{% for article in object_list|slice:":5" %}
<div class="navItem">
<div class="overlay">
</div><!-- /.overlay -->
<img src="{{article.relatedImage}}" alt="" class="navPicture">
<p class="navTitle">{{ article.title|truncatewords:"4" }}</p>
</div><!-- /.navItem -->
{% endfor %}
</div><!-- /.navContent -->
urls.py (which relates to the app called blog)
from django.conf.urls import patterns, url
from . import views
urlpatterns = patterns(
'',
url(r'^$', views.BlogIndex.as_view(), name="list"),
url(r'^(?P<slug>\S+)', views.BlogDetail.as_view(), name="detailed"),
)
The get_context_data method belongs in your view. If you move it there, your sidebar should work.
However, there is a much cleaner way to achieve this by using a context processor. Context processors allow you to make certain data available to all of your templates, no matter where they are.
Create context_processors.py in your project module directory, and make it look something like this:
# myproject/myproject/context_processors.py
from myapp.models import FullArticle
def sidebar_articles(request):
"""Return the 5 most recent articles for the sidebar."""
articles = FullArticle.objects.all()[:5]
return {'SIDEBAR_ARTICLES': articles}
You will have to enable the context processor in your settings.py. Do this by adding the TEMPLATE_CONTEXT_PROCESSORS default setting, and appending your new context processor to the bottom. Like this:
# settings.py
TEMPLATE_CONTEXT_PROCESSORS = (
"django.contrib.auth.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
"django.core.context_processors.media",
"django.core.context_processors.static",
"django.core.context_processors.tz",
"django.contrib.messages.context_processors.messages",
"myproject.context_processors.sidebar_articles",
)
Now you will be able to reference SIDEBAR_ARTICLES from any of your templates. Your sidebar.html could be rewritten as:
<!-- myapp/templates/detailed.html -->
<div>
{% for article in SIDEBAR_ARTICLES %}
<h1>{{ article.title }}</h1>
and so on ...
{% endfor %}
</div>
I believe it is necessary to pass the right arguments to your included template. See https://docs.djangoproject.com/en/dev/ref/templates/builtins/#include for more documentation.
Template will probally look like: {% include "sidebar.html" with article=object %}
Update 1
After reading the docs about detailview, my following suggestions
(https://docs.djangoproject.com/en/1.7/ref/class-based-views/generic-display/#detailview) It appears your context only contains a object. So we have to pass this to this included slider template.
{% include "slider.html" with article=object %}
The problem within your sidebar template is that you don't have an object_list within your context. You can add this to your DetailView as shown in the example. So the method would look like.
def get_context_data(self, **kwargs):
context = super(BlogDetail, self).get_context_data(**kwargs)
context['object_list'] = models.FullArticle.objects.published()
return context
And the include of the template should look like.
{% include "sidebar.html" with object_list=object_list %}
Take care you are passing correctly your context to template. But probably you have to add spaces between '{{' bracket and variable name. Ex. {{ article.title }}
I am making a django blog and want to show a list of comments for each blog post, but I have trouble figuring out how to reference the comments in the views and the templates.
My models are defined like this:
class Issue(models.Model):
title = models.CharField(max_length=255)
text = models.TextField()
author = models.ForeignKey(User)
def __unicode__(self):
return self.title
class Comment(models.Model):
commenter = models.ForeignKey(User)
issue = models.ForeignKey(Issue)
text = models.TextField()
and my views like this
class IssueDetail(DetailView):
model = Issue
context_object_name = "issue"
template_name = "issue_detail.html"
def get_context_data(self, **kwargs):
context = super(IssueDetail, self).get_context_data(**kwargs)
context['comments'] = Comment.objects.all()
return context
class CommentDetail(DetailView):
model = Comment
context_object_name = "comment"
template_name = "comment_detail.html"
and finally the issue_detail.html template
{% block content %}
<h2>{{ issue.title }}</h2>
<br/>
<i>As written by {{ issue.author.first_name }}</i>
<br/><br/>
<blockquote> {{ issue.text }}</blockquote>
<h3>Comments</h3>
{% for comment in comments %}
<li>{{comment}}</li>
{% endfor %}
{% endblock %}
This allows me to reference the fields of the comment inside the Issue template, but basically then I want the comments to have a template of their own that will be rendered inside the for loop. What is the correct way to do this in Django?
The comments are already available in your template because of the model relationship you defined. You can delete the get_context_data in IssueDetail.
Your issue_detail.html template could look like this:
{% for comment in issue.comment_set.all %}
{% include 'comment_detail.html' %}
{% endfor %}
Your comment_detail.html template could look like this:
<ul>
<li>{{ comment.issue }}</li>
<li>{{ comment.text }}</li>
</ul>
what if this we were using a different model
product = models.ForeignKey(Customer)
how would we do the CRUD opertions from the templates an the views.py