Slug URL not working in Django - python

I am trying to setup Slug urls in a Django project. I keep getting a 404 error and can not find what I am missing. I have done this several times before but for some reason I must missing something.
models.py
from django.conf import settings
from django.core.urlresolvers import reverse
from django.db import models
# Create your models here.
class Pages(models.Model):
title = models.CharField(max_length=250, default="Add Title Here")
content = models.TextField(default="Add Content Here")
slug = models.SlugField(unique=True)
seo_title = models.CharField(max_length=250, default="Add Title Here")
seo_description = models.CharField(max_length=160, default="Add Seo Description")
active = models.BooleanField(default=False)
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False, null=True)
updated = models.DateTimeField(auto_now_add=False, auto_now=True, null=True)
def __unicode__(self):
return self.title
class Meta:
unique_together = ('slug', 'title')
def get_absolute_url(self):
return reverse("single_page", kwargs={"slug": self.slug})
views.py
from django.shortcuts import render, get_object_or_404, Http404
# Create your views here.
from .models import Pages
def Single(request, slug):
try:
page = get_object_or_404(Pages, slug=slug)
context = {'page': page}
template = 'pages/page_detail.html'
return render(request, template, context)
except:
raise Http404
urls.py
from django.conf.urls import url, patterns
from . import views
urlpatterns = [
url(r'^(?P<slug>[\w-]+)/$', views.Single, name='single_page'),
]

Thanks guys for your help. I found the issue with some help from #vastlysuperiorma
Views.py
def single(request, slug):
try:
page = get_object_or_404(Pages, slug=slug)
context = {'page': page}
template = 'pages/page_detail.html'
return render(request, template, context)
except Pages.DoesNotExist:
raise Http404

Related

Django URL Path from DB file value

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)
...

How to generate a "fake" slug in Django

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'),

How to edit my views.py so that unauthorized users can see posts made public?

I have added a 'public' attribute to my models.py to allow users to make their posts public. I am trying to modify my views.py so that public posts are visible to unauthorised users.
Here is my code:
views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.http import Http404
from .models import BlogPost
from .forms import BlogPostForm
def check_post_owner(request, post):
if post.owner != request.user:
raise Http404
#login_required()
def index(request):
"""Home Page for blogs"""
posts = BlogPost.objects.filter(owner=request.user).order_by('date_added')
context = {'posts': posts}
return render(request, 'blogs/index.html', context)
models.py
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class BlogPost(models.Model):
"""A blog post"""
title = models.CharField(max_length=30)
text = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
public = models.BooleanField(default=False)
def __str__(self):
return self.title
remove the login required decorator and try this way
def index(request):
"""Home Page for blogs"""
if request.user.is_authenticated:
posts = BlogPost.objects.filter(owner=request.user).order_by('date_added')
else:
posts = BlogPost.objects.filter(public=True).order_by('date_added')
return render(request, 'blogs/index.html', {'posts': posts})
you can filter the BlogPost by (public=True) that should work. so it should be like
posts = BlogPost.objects.filter(owner=request.user, public=True).order_by('date_added')

How can i limit the post view for unauthenticated user in class based view django

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()

How can I create a new url in a Django app?

I have implemented a model in a Django app and I would like add an url to handle the view for this model.
class Post(models.Model):
author = models.ForeignKey('auth.User')
title = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
Can you help me?
I would recommend the Django tutorial as well: https://docs.djangoproject.com/en/1.10/intro/tutorial01/
However, to answer your question, you could do a setup like this:
views.py:
from django.shortcuts import render
from .models import Post
def my_view(request):
posts = Post.objects.all()
return render(request, 'template.html', {'posts': posts})
urls.py:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.my_view, name='my_view')
]

Categories

Resources