Currently I have the following Models:
class Post(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=200, blank=False, null=False)
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, null=False, blank=False)
text = models.TextField(max_length=1000)
and these ModelViewSets:
class PostViewSet(ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
class CommentViewSet(ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
my question is how can I retrieve comments from a post or add new comments in a post with urls like this:
GET /posts/{id}/comments
currently I get the comments this way:
GET /comments/{id} #comment Id, not post id.
current urls.py:
from rest_framework.routers import DefaultRouter
from .views import PostViewSet, CommentViewSet
router = DefaultRouter()
router.register(r'post', PostViewSet, basename='posts')
router.register(r'comments', CommentViewSet, basename='comments')
urlpatterns = []
The first step is to create your view, In this case, you will pass the postId on the URL, this argument will be available inside your view using self.kwargs[] . The get_queryset will return a queryset where you can write any logic, and serialize him.
class MessageList(generics.ListAPIView):
serializer_class = CommentSerializer
def get_queryset(self):
return Comment.objects.filter(post=self.kwargs['post_id'])
Another get_querysetoption is to get your post and then return the massages.
def get_queryset(self):
return Post.objects.get(pk=self.kwargs['post_id']).comment_set.all()
Map this view to an URL, note that the name of the variable are the same used on the view.
path('posts/<post_id>/comments/', MessageList.as_view()),
Related
Urls.py:
urlpatterns = [
path("", views.index, name="blogHome"),
path("blogpost/<int:id>/", views.blogpost, name="blogHome")
]
Views.py:
django.shortcuts import render
from .models import Blogpost
# Create your views here.
def index(request):
return render(request, 'blog/index.html')
def blogpost(request, id):
post.Blogpost.objects.filter(post_id = id)[0]
print(post)
return render(request, 'blog/blogpost.html')
Models.py:
from django.db import models
class Blogpost(models.Model):
post_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=50)
head0 = models.CharField(max_length=500, default="")
chead0 = models.CharField(max_length=10000, default="")
head1 = models.CharField(max_length=500, default="")
chead1 = models.CharField(max_length=10000, default="")
head2 = models.CharField(max_length=500, default="")
chead2 = models.CharField(max_length=10000, default="")
pub_date = models.DateField()
thumbnail = models.ImageField(upload_to='blog/images', default="")
def __str__(self):
return self.title
Error
Error in cmd
Not Found: /blog/blogpost
[21/Jun/2022 12:29:33] "GET /blog/blogpost HTTP/1.1" 404 2678
The current error means Django doesn't find anything with the route blog/blogpost, it is because you have also defined an id to be pass in route, so kindly try http....blog/blogpost/1/ any id you can give.
Also, id is generally used to get a single object, and you are doing filtering on it. I think you should use get_object_or_404 if you want to retrieve single object.
As #lvanStarostin stated in the above comment that URL patterns should also have unique names. You should change one of the names.
Note: Models are classes of python so they must be written in PascalCase, so you may change your model name to BlogPost from Blogpost.
I trying to add slugs to my service page and my project page, but whenever I try to run my project page I get the Page not found (404)
No service found matching the query
Request Method: GET
Request URL: http://127.0.0.1:8000/project/
Raised by: pages.views.<class 'pages.views.ServiceDetail'>
Here's my class-based code
models.py
class Service(models.Model):
title = models.CharField(max_length=50)
photo = models.ImageField(upload_to='photos/%Y/%m/%d/')
alt = models.CharField(max_length=60, blank=True)
icon = models.CharField(max_length=20)
description = RichTextField()
shortdesc = models.CharField(max_length=255)
slug = models.SlugField(null=False, unique=True)
created_date = models.DateTimeField(default=datetime.now, blank=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('service_detail', kwargs={'slug': self.slug})
class Project(models.Model):
title = models.CharField(max_length=50)
category = models.CharField(max_length=50)
photo = models.ImageField(upload_to='photos/%Y/%m/%d/')
alt = models.CharField(max_length=60, blank=True)
client = models.CharField(max_length=50)
launched = models.CharField(max_length=50)
demands = models.CharField(max_length=50)
description = RichTextField()
shortdesc = models.CharField(max_length=255)
slug = models.SlugField(null=False, unique=True)
video_link = models.URLField(max_length=100)
created_date = models.DateTimeField(default=datetime.now, blank=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('project_detail', kwargs={'slug': self.slug})
urls.py
path('<slug:slug>/', views.ServiceDetail.as_view(), name='service_detail'),
path('project/<slug:slug>/', views.ProjectDetail.as_view(), name='project_detail'),
views.py
def project(request):
return render(request, 'project/project.html')
class ProjectDetail (generic.DetailView):
model = Project
template_name = 'project/project_detail.html'
def service(request):
return render(request, 'pages/service.html')
class ServiceDetail (generic.DetailView):
model = Service
template_name = 'pages/service_detail.html'
how can I re-route so that my project page can work out? any help would be grateful
Your url path in urls.py is pointing to path('project/slug:slug/',...), however you are requesting a page without the /slug:slug/ part being passed http://127.0.0.1:8000/project/ (slug part missing). Request http://127.0.0.1:8000/project/slug-name (replace slug-name with a valid slug) and see if it works.
Also see Razensteins answer
The error message has the following reason:
you make a call to "http://127.0.0.1:8000/project/"
your path statement is routing it to 'pages.views.ServiceDetail' - with url parameter slug="project"
path('<slug:slug>/', views.ServiceDetail.as_view(),
Inheritance of ServiceDetail -> DetailView -> BaseDetailView -> SingleObjectMixin (among others..)
as you make a GET request, method get() in BaseDetailView is called:
def get(...)
self.object = self.get_object()
method get_object() in SingleObjectMixin
try:
# Get the single item from the filtered queryset
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") %
{'verbose_name': queryset.model._meta.verbose_name})
queryset.get() tries to find an item with slug="project" which does not exist in database and handles the resulting excpetin whit the 404 response that you get as error message
So to avoid that the call to "http://127.0.0.1:8000/project/" is going to be routed by
path('<slug:slug>/', views.ServiceDetail.as_view().....
you need to change the order of paths:
path('project/<slug:slug> ....
path('<slug:slug> ....
and make a call like "http://127.0.0.1:8000/project/your_project_slug"
With you current order, 'project' is interpreted as slug, because paths are matched one after the other.
I am new to Django and I am creating a simple blog web application. I would like to get the blog post of another user (not the user that is Authenticated) using the get_queryset Method. I tried the script below but, it shows an empty list on the template. I am able to use get_queryset to show all the blogpost, but my main concern is to show all the blogpost of a specific user (not the user that is authenticated)
View.py
class OtherUserProfileView(LoginRequiredMixin, ListView):
model = Post
template_name = "core/otheruser.html"
def get_queryset(self):
queryset = super(OtherUserProfileView, self).get_queryset()
queryset = queryset.filter(pk=self.user.id)
return queryset
Model.py
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=250)
content = models.TextField()
created = models.DateTimeField(auto_now_add=True)
publish = models.BooleanField(blank=True, default=False)
def __str__(self):
return self.title
You can pass the id of the user that you want to filter the queryset by in the url pattern
urlpatterns = [
path('profile/<int:user_id>/', views.OtherUserProfileView.as_view(), name='profile'),
]
In your view you can access the user_id from the path via self.kwargs['user_id'] and use this to filter your queryset
class OtherUserProfileView(LoginRequiredMixin, ListView):
model = Post
template_name = "core/otheruser.html"
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.filter(user_id=self.kwargs['user_id'])
return queryset
I am new to django. In my project I want to make home page which views some of the post.But if user get registered or authenticated then they can view all the post available on the website. so far I have created the view which renders all the post on home page but I want to limit them.
I am using class based view.
posts/views.py
from django.views.generic import ListView, DetailView
from .models import Post
class PostListView(ListView):
model = Post
template_name = 'posts/home.html'
context_object_name = 'posts'
ordering = ['-date_posted']
class PostDetailView(DetailView):
model = Post
template_name = 'posts/post_detail.html'
posts/models.py
from django.db import models
from django.utils import timezone
from slugger import AutoSlugField
from django.contrib.auth.models import User
from django.urls import reverse
# Create your models here.
def upload_location(instance, filename):
return "%s/%s" %(instance.slug, filename)
class Category(models.Model):
title = models.CharField(max_length= 60)
slug = AutoSlugField(populate_from='title')
parent = models.ForeignKey('self',blank=True, null=True ,related_name='children',on_delete=models.CASCADE)
updated = models.DateTimeField(auto_now=True, auto_now_add=False)
timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
def __unicode__(self):
return self.title
def __str__(self):
return self.title
class Post(models.Model):
title = models.CharField(max_length=120)
slug = AutoSlugField(populate_from='title')
image = models.ImageField(
upload_to=upload_location,
null=True,
blank=True,
)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
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("posts-detail", kwargs={"pk": self.pk})
In a ListView, the default QuerySet is all objects. In your case, with the model set to Post, the default queryset is Post.objects.all().
You can override the get_queryset() method of the ListView. Check out this website to get a good understanding of Django CBVs.
def get_queryset(self):
qs = super().get_queryset()
if self.request.user.is_authenticated:
return qs
else:
return qs.filter(<add a filter for not logged in users>)
# of return qs[:10] # to limit to 10 posts
Redirecting the user
You can use the LoginRequiredMixin [Django-doc] to prevent users to see a view if they are not logged in. In that case the default behavior is to redirect to the login page.
You can add the mixin to your views like:
# posts/views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView, DetailView
from .models import Post
class PostListView(LoginRequiredMixin, ListView):
model = Post
template_name = 'posts/home.html'
context_object_name = 'posts'
ordering = ['-date_posted']
class PostDetailView(LoginRequiredMixin, DetailView):
model = Post
template_name = 'posts/post_detail.html'
The documentation describes that you can set the redirect_to class attribute to something else if you want to redirect the user to another page.
Show a page without Posts (or filter these)
You can also decide to render the page, but without any Post objects. We can handle that by patching the get_queryset method:
# posts/views.py
from django.views.generic import ListView, DetailView
from .models import Post
class PostListView(ListView):
model = Post
template_name = 'posts/home.html'
context_object_name = 'posts'
ordering = ['-date_posted']
def get_queryset(self):
if not self.request.user.is_authenticated:
return Post.objects.none()
else:
return super().get_queryset()
class PostDetailView(DetailView):
model = Post
template_name = 'posts/post_detail.html'
def get_queryset(self):
if not self.request.user.is_authenticated:
return Post.objects.none()
else:
return super().get_queryset()
I am using Django Rest Framework CreateAPIView in order to create a comment. So far everything is OK and here is my code.
Models
class Posts(models.Model):
title = models.CharField(max_length=512, null=True)
slug = models.CharField(max_length=512, null=True)
class Comments(models.Model):
post = models.ForeignKey(Posts, on_delete=models.CASCADE)
content = models.CharField(max_length=5000, null=True)
Serializer
class CommentCreateSerializer(ModelSerializer):
class Meta:
model = Comments
fields = [
'content',
'post'
]
and view
class CommentCreateView(CreateAPIView):
permission_classes = [IsAuthenticated]
queryset = Comments.objects.all()
serializer_class = CommentCreateSerializer
I sent a post request to the create route with post(ID) and content and everything worked. But the problem is I wanna pass post slug instead of post ID.
I am not sure how can I do that. I am familiar with lookup_fields but I am not certain how to apply them for ForeignKey match.
You can use SlugRelatedField in CommentCreateSerializer to use slug instead of pk when you pass the post value on Comment Create request, like this:
class CommentCreateSerializer(ModelSerializer):
post = serializers.SlugRelatedField(
queryset=Posts.objects.all(), slug_field='slug'
)
class Meta:
model = Comments
fields = [
'content',
'post'
]
In the CommentAPIview you need to overwrite the perform create method in to the lookup like so
def perform_create(self):
post_pk = self.kwargs.get("post_pk")
post = get_object_or_404(Post, pk=post_pk)
serializer.save(post=post)