One of the things I like about dev.to is their URL design. It combines a slug with a hashed value representing an internal representative of some kind of index value to ensure uniqueness. You can see an example of it in my post "Autodocumenting Makefiles" featured in the URL "https://dev.to/feldroy/autodocumenting-makefiles-175b".
Let's break apart that URL:
feldroyis my company name.
autodocumenting-makefilesis the slug and it's based off the article title.
175bis a hashed value that is either stored in an indexed character field or broken down by the router into a numeric primary key.
Here is another way of looking at their URL design:
Let's see how we can implement a simplified version of this technique using Django.
We're going with a simpler version of the Dev.to implementation. Our implementation will use the database primary key to ensure uniqueness instead of the hashed value relied on by Dev.to.
Okay, now that we've determined our URL design, let's build it!
Store the data!
# articles/models.py from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ class Article(models.Model): title = models.CharField(_("Title"), max_length=100) slug = models.CharField(_("Slug"), max_length=100) author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) # More fields...
Collect and validate the data!
# articles/forms.py from django import forms from .models import Article class ArticleForm(forms.ModelForm): class Meta: model = Article fields = ('title', ) # more fields
Now that we have the model and form, let's build the views:
# articles/views.py from django.shortcuts import get_object_or_404 from django.contrib.auth.mixins import LoginRequiredMixin from django.utils import slugify from django.views.generic import CreateView, DetailView, UpdateView from .forms import ArticleForm from .models import Article class ArticleCreateView(LoginRequiredMixin, CreateView): model = Article form_class = ArticleForm def form_valid(self, form): # Save the data to an article object - # this hasn't yet saved to the database. article = form.save(commit=False) article.slug = slugify(article.title) article.author = self.request.user # Save again - this time to the database article.save() return super().form_valid(form) class ArticleUpdateView(LoginRequiredMixin, UpdateView): model = Article form_class = ArticleForm def get_object(self): # We get records by primary key, which ensures that # changes in the title or slug doesn't break links return get_object_or_404(Article, id=self.kwargs['pk'], author__username=self.kwargs['username'], author=self.request.user ) def form_valid(self, form): # Update the slug if the title has changed. article = form.save(commit=False) article.slug = slugify(article.title) article.save() return super().form_valid(form) class ArticleDetailView(DetailView): model = Article def get_object(self): # We get records by primary key, which ensures that # changes in the title or slug doesn't break links return get_object_or_404(Article, id=self.kwargs['pk'], author__username=self.kwargs['username'] )
Let's route this into our urls:
# articles/urls.py from django.urls import path from articles import views urlpatterns = [ path(route='/new/', view=views.ArticleCreateView.as_view(), name='create', ), path(route='/<slug:username>/<slug:slug>-<int:pk>/edit/', view=views.ArticleUpdateView.as_view(), name='update', ), path(route='/<slug:username>/<slug:slug>-<int:pk>/', view=views.ArticleDetailView.as_view(), name='detail', ), ]
And in the project's root config, we add in this:
# config/urls.py or wherever you stick the project's root urls.py from django.conf.urls.static import static from django.contrib import admin from django.urls import include, path urlpatterns = [ # Django Admin, change this URL path('two-scoops-of-django-is-awesome', admin.site.urls), # Articles management path('', include('articles.urls', namespace='article')), # More URLS here ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # There's certainly more URLs down here
Add templates and there it is, a Django example that follows the Dev.to URL design!
Experienced Django users will know that the slugification logic in the
form_valid methods properly belongs in the Article model's
save() method, rather than the views. That's the kind of thing we discuss in Two Scoops of Django.