New to Django, this is a simple blog post app. How do i include the name of a post's author in the url?
urlpatterns = [
path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
]
post model
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=100, blank="true")
content = models.CharField(max_length=400)
views.py
class PostDetailView(DetailView):
model = Post
You can add an extra parameter:
urlpatterns = [
path('post/<str:author>/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
]
in the view, you can filter on the author such that if the author's username is incorrect, it will not present any content:
class PostDetailView(DetailView):
model = Post
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
author__username=self.kwargs['author']
)
We can generate a URL for a Post object with the .get_absolute_url() method [Django-doc] which includes the username of the author:
from django.urls import reverse
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=100, blank="true")
content = models.CharField(max_length=400)
def get_absolute_url(self):
return reverse('post-detail', kwargs={'id': self.pk, 'author': self.author.username})
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Related
I'm trying to link a "normal" model field with an admin model field, for example I have a table "Post" and I want to add the admin username as a ForeignKey to the field "Author" of the table Post.
I mean :
class Post(models.Model):
title = models.CharField(max_length=50)
body = RichTextField(blank=True, null=True)
date = models.DateTimeField('date_posted')
username = models.ForeignKey(admin.username, on_delete=models.CASCADE)
Where admin.username refers the username of auth_user admin model
Thanks for your help
As the referencing the user model section of the documentation says, you can make use of settings.AUTH_USER_MODEL to obtain a reference to the user model that is used. You can use the to_field=… [Django-doc] to specify to what field of the model it should refer, so:
from django.conf import settings
class Post(models.Model):
title = models.CharField(max_length=50)
body = RichTextField(blank=True, null=True)
date = models.DateTimeField('date_posted')
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
to_field='username'
on_delete=models.CASCADE,
editable=False
)
By specifying editable=False [Django-doc] it will not automatically show up in ModelForms.
In views, you can then set the logged in user as author by specifing the author attribute. For example:
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
#login_required
def some_view(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
form.instance.author = request.user
form.save()
return redirect('name-of-some-view')
else:
form = PostForm()
return render(request, 'some_template.html', {'form': form})
Note: A ForeignKey does not store the string representation (or name) of the
referenced object in the column, it stores the primary key of the record it
references in a column with an _id suffix to a ForeignKey field. Therefore
ForeignKeys usually do not end with a _name suffix. You might want to
consider renaming the username field to author.
How about something like this?
class Post(models.Model):
title = models.CharField(max_length=50)
body = RichTextField(blank=True, null=True)
date = models.DateTimeField('date_posted')
user = models.ForeignKey(auth_user, on_delete=models.CASCADE)
#property
def username(self): return self.user.username
Usage:
some_post = Post.objects.get(id='the_post_id')
print(some_post.username) # prints some_post.user.username
Im trying to create an endpoint for a post and its comments in the following format:
/posts (view all posts)
/posts/{id} (view post by id)
/posts/{id}/comments (view comments for a post)
The first 2 work, but for the last one I have /comments rather than the url i would like and I am not sure how to go about that, I think I need to change my models for it.
My current models (its using default Django User):
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=255)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
class PostComment(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
comment = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.post.title
And urls:
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'posts', views.PostViewSet)
router.register(r'comments', views.PostCommentViewSet)
Edit: this are the viewsets
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all().order_by('id')
serializer_class = UserSerializer
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all().order_by('created_at')
serializer_class = PostSerializer
class PostCommentViewSet(viewsets.ModelViewSet):
queryset = PostComment.objects.all().order_by('created_at')
serializer_class = PostCommentSerializer
You can achieve this by writing the custom viewset actions--(drf doc),
from rest_framework.decorators import action
from rest_framework.response import Response
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all().order_by('created_at')
serializer_class = PostSerializer
#action(detail=True, url_path='comments', url_name='post-comments')
def comments(self, request, *args, **kwargs):
queryset = PostComment.objects.filter(post=kwargs['pk'])
serializer = PostCommentSerializer(queryset, many=True, context= {'request':request, 'view':self})
return Response(data=serializer.data)
Your view should be something similar to this -
class PostCommentViewSet(viewsets.ModelViewSet):
queryset = PostComment.objects.all().order_by('created_at')
serializer_class = PostCommentSerializer
#action(detail=True)
def comments(self, request, id=None):
....
You can refer to DRF documentation for more detail here - https://www.django-rest-framework.org/api-guide/routers/#routing-for-extra-actions
If you want to use the router, then this is probably achievable by implementing a custom router, like in this example: https://www.django-rest-framework.org/api-guide/routers/#example
I think you forgot to register the viewset route parameters with the action decorator
https://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing
it should work if you had in your viewset
from rest_framework.decorators import action
#actions(detail=True)
def comments(self, request, pk):
# things to do here
So I'm trying to achieve the general "Like" functionality in a social media website using Django and REST Framework, and a frontend in React.
Using a Post model to save all the posts, and I have a Many-to-Many field for storing the likes and created a through model as follows:
class PostLike(models.Model):
user = models.ForeignKey(AppUser, on_delete=models.CASCADE)
post = models.ForeignKey("Post", on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
class Post(models.Model):
user = models.ForeignKey(AppUser, on_delete=models.CASCADE)
caption = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
edited_at = models.DateTimeField(auto_now=True)
likes = models.ManyToManyField(
AppUser, related_name="post_user", blank=True, through=PostLike
)
(AppUser is a custom auth model used)
Similarly, I have created serializers and viewsets for the above models:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = "__all__"
class PostLikeSerializer(serializers.ModelSerializer):
class Meta:
model = PostLike
fields = "__all__"
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
class PostLikeViewSet(viewsets.ModelViewSet):
queryset = PostLike.objects.all()
serializer_class = PostLikeSerializer
My question is, how do I "like" or remove an existing "like" from a post using API calls?
One method I know is to just make a POST request to the PostLike endpoint using the user PK and the post PK to create a PostLike instance, but I don't know a way to "remove" a like using the same method.
Please help!
you can use APIView instead of ViewSet like this:
from rest_framework import views
class PostLikeApiView(views.APIView):
serializer = PostLikeSerializer(data=request.data)
if serializer.is_valid():
user = serializer.data['user']
post = serializer.data['post']
post_like_obj = PostLike.objects.filter(user=user, post=post)
if post_like_obj.exists():
post_like_obj.delete()
result = 'unliked'
else:
PostLike.objects.create(user=user, post=post)
result = 'liked'
return Response(
{
'result': result,
},
status=status.HTTP_200_OK
)
else:
return Response(
serializer.errors,
status=status.HTTP_400_BAD_REQUEST
)
I am the newbie of writing programming, now I am learning django.
I have a problem for URL redirection. I create the model and it does work at admin site.
Also I set the PK for each article, that successfully generate the URL by PK.
However when I post the message form the front-end, after posting it appear the error message suppose it should be redirect to the page of DetailViewand
I have imported the reverse function in my model, but it seem not working.
My python version : 3.7.6 and django version : 3.0.0
ImproperlyConfigured at /add/
No URL to redirect to. Either provide a url or define a get_absolute_url method on the Model.
My View
from django.shortcuts import render
from django.views.generic import ListView, DetailView
from django.views.generic.edit import CreateView
from .models import Page
class PageListView(ListView):
model = Page
template_name='home.html'
context_object_name = 'all_post_list'
class PageDetailView(DetailView):
model = Page
template_name='post.html'
class PageCreateView(CreateView):
model = Page
template_name='post_new.html'
fields = ['title', 'author', 'body', 'body2']
Model
from django.urls import reverse
from django.db import models
from ckeditor.fields import RichTextField
class Page(models.Model):
title = models.CharField(max_length=50)
author = models.ForeignKey(
'auth.User',
on_delete=models.CASCADE,
)
body = RichTextField()
body2 = models.TextField()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post', args=[str(self.id)])
URL
from django.urls import path
from .views import PageListView, PageDetailView, PageCreateView
urlpatterns = [
path('add/', PageCreateView.as_view(), name='post_new'),
path('', PageListView.as_view(), name='home'),
path('blog/<int:pk>/', PageDetailView.as_view(), name='post'),
]
Thanks for helping. :)
I think your indentation is the problem here. Fix it by:
class Page(models.Model):
title = models.CharField(max_length=50)
author = models.ForeignKey(
'auth.User',
on_delete=models.CASCADE,
)
body = RichTextField()
body2 = models.TextField()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post', args=[self.id])
I want to use HyperlinkedIdentityField in my serializer, that's why I need view name. How can I get the view name if I need model_name-list for example?
Here is my serializer:
class CategorySerializer(serializers.HyperlinkedModelSerializer):
posts = serializers.HyperlinkedIdentityField(view_name='', format=None)
class Meta:
model = Category
fields = ('url', 'category_name', 'id', 'parent', 'posts')
This is my views.py:
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
And here is my urls.py:
router = routers.DefaultRouter()
router.register(r'categories', views.CategoryViewSet)
router.register(r'posts', views.PostViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
]
My models:
class Post(models.Model):
post_name = models.CharField(max_length=50)
post_text = models.CharField(max_length=1000)
pub_date = models.DateTimeField('date published')
categories = models.ManyToManyField('Category', related_name='posts')
def __str__(self):
return self.post_name
class Category(models.Model):
category_name = models.CharField(max_length=50)
parent = models.ForeignKey('self', null=True, blank=True, on_delete = models.CASCADE)
def __str__(self):
return self.category_name
I need a view_name for post-list, in order to use HyperlinkedIdentityField
As the Documentation for routers in DRF (http://www.django-rest-framework.org/api-guide/routers/) says, if the base_name argument is not passed when registering the ViewSet then the view_name is auto generated depending on the queryset attribute of the ViewSet registered while defining the router.
In your case the view_name should be 'post-list' for the list view and 'post-detail' for the detail view of the viewset unless you want to change/override it by passing it as the third parameter to router.register().
Also note that the queryset attribute should always be present if you are not initializing the base_name for the ViewSet which otherwise will throw an error.