I saw in a video I didn't save that you could do something like this:
www.mysite.com/post/12345/this-is-a-title
Where the 12345 is the id and what actually is used to do the query and the this-is-a-tilte part is automatically assigned when loading the page and you can actually write whatever you want there and will still load the page.
How do I set this up in my path and do I still need a SlugField?
If you have a model with a title:
class Post(models.Model):
title = models.CharField(max_length=128)
# …
You can make a path that looks like:
urlpatterns = [
# …
path('post/<int:pk>/<slug:slug>/', post_detail, name='post_detail'),
]
the view can then fetch the corresponding Post object and slugify the title. If the slug does not match, it redirects it to the correct slug:
from django.shortcuts import get_object_or_404, redirect
from django.utils.text import slugify
def post_detail(request, pk, slug):
post = get_object_or_404(Post, pk=pk)
post_slug = sluglify(post.title)
if slug != post_slug:
# in case the slug does not match, redirect with the correct slug
return redirect('post_detail', pk=pk, slug=post_slug)
# … logic to render the object …
you'll need SlugField field and the slugify function to auto-generate the slug from the title.
try the code below
models.py:
from django.db import models
from django.template.defaultfilters import slugify
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
class Post(models.Model):
"""A model holding common fields to Post model."""
slug = models.SlugField(_('slug'), max_length=255,
unique=True, null=True, blank=True,
help_text=_(
'If blank, the slug will be generated automatically '
'from the given title.'
)
)
title = models.CharField(_('title'), max_length=255,
unique=True,
help_text=_('The title of the post.')
)
[..]
def __str__(self):
return self.title
# Where the magic happens ..
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
super(Post, self).save(*args, **kwargs)
urls.py:
path('post/<id:pk>/<slug:slug>/', views.post_detail, name='post_detail'),
Related
I am trying to create 'project' pages that have their paths generated with the
{{ project.title }} values, rather than the current method I have which uses ints. I don't quite understand how I can do this, but feel I am close?
Models.py
from django.db import models
# Create your models here.
class Project(models.Model):
title = models.CharField(max_length=100)
description = models.TextField()
technology = models.CharField(max_length=20)
image = models.FilePathField(path='projects/static/img/')
live = models.URLField()
source = models.URLField()
def __str__(self):
return self.title
Urls.py
from django.urls import path
from . import views
urlpatterns = [
path("", views.project_index, name="projects"),
path("<int:pk>/", views.project_details, name="project_details"), # PK for Primary Key
]
Views.py
from django.shortcuts import render
from .models import Project
# Create your views here.
def project_index(request):
projects = Project.objects.all()
context = {'projects': projects}
return render(request, 'projects/project_index.html', context)
def project_details(request, pk):
project = Project.objects.get(pk=pk)
context = {'project': project}
return render(request, 'projects/project_details.html', context)
I figure path("<int:pk>/", will need to be a slug, but I just cannot figure out how to tie in the DB data.
Potentially context = {'project': project}?
Currently the url is http://127.0.0.1:8000/projects/1/ - I am looking for http://127.0.0.1:8000/projects/EXAMPLE/
Thanks
You have to add a SlugField to your models.py file:
Models.py
from django.db import models
from django.utils.text import slugify
# Create your models here.
class Project(models.Model):
title = models.CharField(max_length=100)
description = models.TextField()
technology = models.CharField(max_length=20)
image = models.FilePathField(path='projects/static/img/')
live = models.URLField()
source = models.URLField()
slug = models.SlugField(default="", blank=True, null=False, db_index=True)
def __str__(self):
return self.title
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super().save(*args, **kwargs)
Views.py
from django.shortcuts import render
from .models import Project
# Create your views here.
def project_index(request):
projects = Project.objects.all()
context = {'projects': projects}
return render(request, 'projects/project_index.html', context)
def project_details(request, slug):
project = Project.objects.get(slug=slug)
context = {'project': project}
return render(request, 'projects/project_details.html', context)
Urls.py
from django.urls import path
from . import views
urlpatterns = [
path("", views.project_index, name="projects"),
path("<slug:slug>/", views.project_details, name="project_details"),
]
Make sure to run makemigrations and then migrate.
urls.py
path("<title>/", views.project_details, name="project_details"),
views.py
def project_details(request, title: str):
project = Project.objects.filter(title=title).first()
if project is None:
titles = list(Project.objects.all().values_list('title', flat=True))
msg = f'Project(title=title) not found. Exist titles are: {titles}'
raise Exception(msg)
...
I am developing an app that needs users to login and create posts. The post model has an image and a caption (the user inputs) and a profile foreign key that should to automatically pick the logged in users profile.
The app however isnt autopicking the profile
Can someone spot what am doing wrong? I feel like the particular issue is in this line of code in my views
form.instance.profile = self.request.Image.profile
models
from django.db import models
from django.contrib.auth.models import User
import PIL.Image
from django.urls import reverse
# Create your models here.
class Image(models.Model):
image = models.ImageField(upload_to='images/')
caption = models.TextField()
profile = models.ForeignKey('Profile', default='1', on_delete=models.CASCADE)
likes = models.ManyToManyField(User, blank=True)
created_on = models.DateTimeField(auto_now_add=True)
def get_absolute_url(self):
return reverse('vinsta-home')
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
photo = models.ImageField(upload_to = 'photos/',default='default.jpg')
bio = models.TextField(max_length=500, blank=True, default=f'I love vinstagram!')
def __str__(self):
return f'{self.user.username}'
views
from django.shortcuts import render
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.views.generic import (ListView,CreateView,)
from .models import Image
def home(request):
context = {
'posts': Image.objects.all()
}
return render(request, 'vinsta/home.html', context)
class ImageListView(ListView):
model = Image
template_name = 'vinsta/home.html' # <app>/<model>_<viewtype>.html
context_object_name = 'posts'
ordering = ['-created_on']
class ImageCreateView(LoginRequiredMixin, CreateView):
model = Image
fields = ['image', 'caption']
def form_valid(self, form):
form.instance.profile = self.request.Image.profile
return super().form_valid(form)
I think you're just about right as to which line isn't working. Instead of
form.instance.profile = self.request.Image.profile
try
form.instance.profile = Profile.objects.get(user=self.request.user)
(Don't forget to add the import for your Profile model.) I don't think Image exists as a property of request, but user does.
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 gettting this error although I am giving proper string in Text and Title fields.I don't know what are the reasons,However i am able to post the values of remaining three fields(author,Created_date,publish_date).
Serializer.py
from rest_framework import serializers
from blog.models import Post
from django.utils import timezone
from rest_framework.validators import UniqueValidator
class blogSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('author','title', 'text','created_date','published_date')
model.py
from django.db import models
from django.utils import timezone
class Post(models.Model):
author = models.ForeignKey('auth.User',null=True)
title = models.CharField(max_length=200)
text = models.CharField(max_length=200)
created_date = models.DateTimeField(
default=timezone.now)
published_date = models.DateTimeField(
blank=True,null=True)
def publish(self):
self.published_date = timezone.now()
self.save()
def __str__(self):
return self.title
views.py
from blog.models import Post
from .serializers import UserSerializer, GroupSerializer,blogSerializer
from rest_framework import viewsets,status
from rest_framework.decorators import api_view
class blogViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = blogSerializer
here i am posting the data.
#api_view(['GET', 'POST'])
def blog_list(request):
if request.method=='GET':
my_blog=Post.objects.all()
serializers=blogSerializer(my_blog,Many=True)
return JsoResponse(serializers.data,safe=False)
elif request.method=='POST':
data=JSONParser().parse(request)
serializers=blogSerializer(data=data)
if serializers.is_valid():
serializer.save()
return JsonResponse(serializers.data,status=201)
return JsonResponse(serializers.errors,status=400)
I would set up a unit test to test this functionality. It looks like none of your post data is getting to the serializer otherwise the valid fields would have been repopulated once you submit the form and the for is returned with errors. The title and text fields are also the only fields which are required/do not have a default value.
If you are using some form of decorator(or something similar) on the view which accesses request.data, this might be what is causing the issue. Since rest framework modifies request.POST when populating request.data
I just started using Django for some time now and i stuck trying to work with slug. I know what they are, but it's dificult for me to define a simple slug and display it on my browser.
Here is the scenario: i've a models.py containing a class book with 3 fields: name, price and Autor. I want to retunr a slug string for name and autor with only hyphens (-) and lowercase letters.
My question is how to achieve this. Using only the model, the views and the html. This is what i've got till now. But don't know how to get it displayed on my browser(localhost:8000/book_name)
class Book(models.Model):
name = models.CharField(max_length=100)
price = models.IntegerField(default=0)
autor = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
#models.permalink
def get_absolute_url(self):
return 'app:book', (self.slug,)
from django.template.defaultfilters import slugify
Add the following to your model:
def save(self,*args,**kwargs):
self.slug = slugify(self.name)
return super(Book,self).save(*args,**kwargs)
Then you can use the slug in your urls:
url(r'mybooks/(?P<slug>[-\w]+)/$', views.BookDetailView.as_view(),name='book_details'),
Views:
from django.views import generic
class BookDetailView(generic.DetailView):
template_name = "templates/book_detail.html"
You may add this to your models.py file:
def save(self , *args , **kwargs):
if not self.slug:
self.slug=slugify(self.name)
super(Book, self).save(*args, **kwargs)
It worked for me.