반응형
URL Pattern에 Slug를 포함하는 이유
URL에 Slug를 포함하게 되면 검색엔진최적화(SEO)를 개선하는 것에 도움이 되고 좀 더 직관적이게 사용자가 어느 페이지를 방문하고 있는지를 알 수 있어 사용자 친화적일 수 있다.
pk와 slug을 동시에 사용하는 방법으로 로직을 작성하는것을 전제로 하고 있다.
Django ORM을 사용하기 때문에 pk를 노출하는건 보안상 문제가 되지 않지만 불편하다면 해당 값을 해시 데이터로 암호화하여 노출시키거나 리버스 프록시 설정을 하여 URL을 숨기는 방법이 있다.
Post Model에 Slug를 적용하기
models.py
class Post(models.Model):
slug = models.SlugField(
max_length=120,
allow_unicode=True,
help_text="title 필드로부터 자동 생성합니다.",
)
def slugify(self, force=False):
if force or not self.slug:
self.slug = slugify(self.title, allow_unicode=True)
self.slug = self.slug[:120]
def save(self, *args, **kwargs):
self.slugify()
super().save(*args, **kwargs)
- pk를 그대로 활용하면서 slug를 사용하기 때문에 unique 옵션은 적용하지 않는다.
- 레코드를 생성할 때 slugify를 호출하여 title 필드를 slug로 변환해준다.
- 마이그레이션 파일을 생성할 때 slug의 기본 값은 빈 문자열로 할당한다.
0004_post_slug.py
# Generated by Django 4.2.7 on 2024-04-06 19:15
from django.db import migrations, models
from django.utils.text import slugify
def update_slug_if_empty(apps, schema_editor):
Post = apps.get_model("blog", "Post")
post_qs = Post.objects.filter(slug="")
for post in post_qs:
post.slug = slugify(post.title, allow_unicode=True)
Post.objects.bulk_update(post_qs, ["slug"])
class Migration(migrations.Migration):
dependencies = [
("blog", "0003_category_post_category"),
]
operations = [
migrations.AddField(
model_name="post",
name="slug",
field=models.SlugField(
allow_unicode=True,
default="",
help_text="title 필드로부터 자동 생성합니다.",
max_length=120,
),
preserve_default=False,
),
migrations.RunPython(update_slug_if_empty, migrations.RunPython.noop),
]
마이그레이션시 이미 생성된 게시글에 slug를 추가하는 작업 또한 추가하도록 한다.
urls.py
from django.urls import path
from blog import views
app_name = "blog"
urlpatterns = [
path("<int:pk>/", views.post_detail, name="post_detail"),
path("<int:pk>/<str:slug>/", views.post_detail, name="post_detail"),
]
테스트를 위한 패턴 설정을 해주도록 한다.
views.py
from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404, redirect
from django.views.generic import DetailView
from blog.models import Post
# FBV
def post_detail(request, pk, slug=None):
post = get_object_or_404(Post, pk=pk)
if post.slug and (slug is None or post.slug != slug):
return redirect("blog:post_detail", pk=pk, slug=post.slug, permanent=True)
return HttpResponse(f"{post.pk}번 글의 {post.slug}")
# CBV
class PostDetailView(DetailView):
model = Post
context_object_name = "post"
slug_url_kwarg = "slug"
pk_url_kwarg = "pk"
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if self.object.slug and (
self.kwargs.get(self.slug_url_kwarg) is None
or self.object.slug != self.kwargs.get(self.slug_url_kwarg)
):
return redirect(
"blog:post_detail",
pk=self.object.pk,
slug=self.object.slug,
permanent=True,
)
return HttpResponse(f"{self.object.pk}번 글의 {self.object.slug}")
post_detail = PostDetailView.as_view()
테스트를 위하여 뷰 설정도 해주도록 한다.
결과
blog/pk/ 주소로 접속 시 Post 모델에 slug 필드가 존재한다면 자동으로 리다이렉트가 되어 이동하는 것을 확인할 수 있다.
'기술정리 > Django' 카테고리의 다른 글
Django 테이블 조회 n + 1 문제 해결하기 (1) | 2024.04.09 |
---|---|
Django ManyToManyField 커스텀 모델 이전하기 (0) | 2024.04.08 |
Django 안정적인 마이그레이션 방법 (0) | 2024.04.06 |
Django 효율적인 대량 데이터 마이그레이션 방법 (0) | 2024.04.05 |
Django Instagram Project 2 (0) | 2022.11.20 |