How to create a unique slug in Django
I am trying to create a unique slug in Django so that I can access a post via a url like this: http://www.example.com/buy-a-new-bike_Boston-MA-02111_2
The relevant models:
class ZipCode(models.Model):
zipcode = models.CharField(max_length=5)
city = models.CharField(max_length=64)
statecode = models.CharField(max_length=32)
class Need(models.Model):
title = models.CharField(max_length=50)
us_zip = models.CharField(max_length=5)
slug = ?????
def get_city():
zip = ZipCode.objects.get(zipcode=self.us_zip)
city = "%s, %s %s" % (zip.city, zip.statecode, zip.zipcode)
return city
A sample ZipCode record:
- zipcode = "02111"
- city = "Boston"
- statecode = "MA"
A sample Need record开发者_开发知识库:
- title = "buy a new bike"
- us_zip = "02111"
- slug = "buy-a-new-bike_Boston-MA-02111_2" (desired)
Any tips as to how to create this unique slug? Its composition is:
- Need.title + "_" + Need.get_city() + "_" + an optional incrementing integer to make it unique. All spaces should be replaced with "-".
NOTE: My desired slug above assumes that the slug "buy-a-new-bike_Boston-MA-02111" already exists, which is what it has the "_2" appended to it to make it unique.
I've tried django-extensions, but it seems that it can only take a field or tuple of fields to construct the unique slug. I need to pass in the get_city() function as well as the "_" connector between the title and city. Anyone solved this and willing to share?
Thank you!
UPDATE
I'm already using django-extensions for its UUIDField, so it would be nice if it could also be usable for its AutoSlugField!
I use this snippet for generating unique slug and my typical save method look like below
slug will be Django SlugField with blank=True but enforce slug in save method.
typical save method for Need model might look below
def save(self, **kwargs):
slug_str = "%s %s" % (self.title, self.us_zip)
unique_slugify(self, slug_str)
super(Need, self).save(**kwargs)
and this will generate slug like buy-a-new-bike_Boston-MA-02111 , buy-a-new-bike_Boston-MA-02111-1 and so on. Output might be little different but you can always go through snippet and customize to your needs.
My little code:
def save(self, *args, **kwargs):
strtime = "".join(str(time()).split("."))
string = "%s-%s" % (strtime[7:], self.title)
self.slug = slugify(string)
super(Need, self).save()
If you are thinking of using an app to do it for you, here is one.
https://github.com/un33k/django-uuslug
UUSlug = (``U``nique + ``U``code Slug)
Unicode Test Example
=====================
from uuslug import uuslug as slugify
s = "This is a test ---"
r = slugify(s)
self.assertEquals(r, "this-is-a-test")
s = 'C\'est déjà l\'été.'
r = slugify(s)
self.assertEquals(r, "c-est-deja-l-ete")
s = 'Nín hǎo. Wǒ shì zhōng guó rén'
r = slugify(s)
self.assertEquals(r, "nin-hao-wo-shi-zhong-guo-ren")
s = '影師嗎'
r = slugify(s)
self.assertEquals(r, "ying-shi-ma")
Uniqueness Test Example
=======================
Override your objects save method with something like this (models.py)
from django.db import models
from uuslug import uuslug as slugify
class CoolSlug(models.Model):
name = models.CharField(max_length=100)
slug = models.CharField(max_length=200)
def __unicode__(self):
return self.name
def save(self, *args, **kwargs):
self.slug = slugify(self.name, instance=self)
super(CoolSlug, self).save(*args, **kwargs)
Test:
=====
name = "john"
c = CoolSlug.objects.create(name=name)
c.save()
self.assertEquals(c.slug, name) # slug = "john"
c1 = CoolSlug.objects.create(name=name)
c1.save()
self.assertEquals(c1.slug, name+"-1") # slug = "john-1"
Here are a couple functions that I use. You pass in the model instance and the desired title into unique_slugify
which will add the slug if it doesn't exist, otherwise it will continue trying to append a 4 digit random string until it finds a unique one.
import string
from django.utils.crypto import get_random_string
def unique_slugify(instance, slug):
model = instance.__class__
unique_slug = slug
while model.objects.filter(slug=unique_slug).exists():
unique_slug = slug + get_random_string(length=4)
return unique_slug
I usually use it by overriding the model save
method.
class YourModel(models.Model):
slug = models.SlugField()
title = models.CharField()
def save(self, *args, **kwargs):
if not self.slug:
self.slug = unique_slugify(self, slugify(self.title))
super().save(*args, **kwargs)
This is the simple and small code i am using for generating unique slug, you only need one field to create your unique slug field
from random import randint
def save(self, *args, **kwargs):
if Post.objects.filter(title=self.title).exists():
extra = str(randint(1, 10000))
self.slug = slugify(self.title) + "-" + extra
else:
self.slug = slugify(self.title)
super(Post, self).save(*args, **kwargs)
I hope you like this.
This is a simple implementation that generate the slug from the title, it doesn't depend on other snippets:
from django.template.defaultfilters import slugify
class Article(models.Model):
...
def save(self, **kwargs):
if not self.slug:
slug = slugify(self.title)
while True:
try:
article = Article.objects.get(slug=slug)
if article == self:
self.slug = slug
break
else:
slug = slug + '-'
except:
self.slug = slug
break
super(Article, self).save()
Django provides a SlugField model field to make this easier for you. Here's an example of it in a "blog" app's
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField(blank=True)
slug = models.SlugField(unique=True)
@models.permalink
def get_absolute_url(self):
return 'blog:post', (self.slug,)
Note that we've set unique=True for our slug field — in this project we will be looking up posts by their slug, so we need to ensure they are unique. Here's what our application's views.py might look like to do this:
from .models import Post
def post(request, slug):
post = get_object_or_404(Post, slug=slug)
return render(request, 'blog/post.html', {
'post': post,
})
from django.utils.text import slugify Helps a lot and has quite clear Concepts. Here one example on How to auto-generate slug by using from django.utils.text import slugify
utils.py
from django.utils.text import slugify
import random
import string
# Random string generator
def random_string_generator(size=10, chars=string.ascii_lowercase + string.digits):
return ''.join(random.choice(chars) for _ in range(size))
# Unique Slug Generator
def unique_slug_generator(instance, new_slug=None):
"""
It assumes your instance has a model with a slug field and a title character (char) field.
"""
if new_slug is not None:
slug = new_slug
else:
slug = slugify(instance.title)
Klass = instance.__class__
qs_exists = Klass.objects.filter(slug=slug).exists()
if qs_exists:
new_slug = "{slug}-{randstr}".format(slug=slug, randstr=random_string_generator(size=4))
return unique_slug_generator(instance, new_slug=new_slug)
return slug
models.py
from django.db.models.signals import pre_save # Signals
# import the unique_slug_generator from .utils.py
from .utils import unique_slug_generator
class Product(models.Model):
title = models.CharField(max_length=120)
# set blank to True
slug = models.SlugField(blank=True, unique=True)
def product_pre_save_receiver(sender, instance, *args, **kwargs):
if not instance.slug:
instance.slug = unique_slug_generator(instance)
pre_save.connect(product_pre_save_receiver, sender=Product)
Django documentation explains Django.utils.text import slugify to generate slug automatically. You can read more detail here
After implementing the code, while creating product, you may leave the slug field blank, which will be further aquired with auto generated slug for the product which will be unique in this case.
Hi can you tried this function
class Training(models.Model):
title = models.CharField(max_length=250)
text = models.TextField()
created_date = models.DateTimeField(
auto_now_add=True, editable=False, )
slug = models.SlugField(unique=True, editable=False, max_length=250)
def __unicode__(self):
return self.title
def get_unique_slug(id,title,obj):
slug = slugify(title.replace('ı', 'i'))
unique_slug = slug
counter = 1
while obj.filter(slug=unique_slug).exists():
if(obj.filter(slug=unique_slug).values('id')[0]['id']==id):
break
unique_slug = '{}-{}'.format(slug, counter)
counter += 1
return unique_slug.
def save(self, *args, **kwargs):
self.slug =self.get_unique_slug(self.id,self.title,Training.objects)
return super(Training, self).save(*args, **kwargs)
def get_slug(self):
slug = slugify(self.title.replace("ı", "i"))
unique = slug
number = 2
while Model.objects.filter(slug=unique).exists():
unique = "{}-{}".format(slug, number)
number += 1
return unique
Best solution for me:
def get_slug(self):
slug = slugify(self.title)
unique_slug = slug
number = 1
while Recipe.objects.filter(slug=unique_slug).exists():
unique_slug = f'{slug}-{number}'
number += 1
return unique_slug
def save(self, *args, **kwargs):
if not self.slug:
self.slug = self.get_slug()
return super().save(*args, **kwargs)
This code can generate slug like this:
- string-slug
- string-slug-1 (if previous alredy exists)
- string-slug-2 (if previous alredy exists)
- and so on...
class Need(models.Model):
title = models.CharField(max_length=50)
us_zip = models.CharField(max_length=5)
slug = models.SlugField(unique=True)
def save(self, **kwargs):
slug_str = "%s %s" % (self.title, self.us_zip)
super(Need, self).save()
Try this, worked out for me,welcome in advance:
class Parcel(models.Model):
title = models.CharField(max_length-255)
slug = models.SlugField(unique=True, max_length=255)
weight = models.IntegerField()
description = models.CharField(max_length=255)
destination = models.CharField(max_length=255)
origin = models.CharField(max_length=255)
def __str__(self):
return self.description
def save(self, *args, **kwargs):
if not self.slug:
t_slug = slugify(self.title)
startpoint = 1
unique_slug = t_slug
while Parcel.objects.filter(slug=unique_slug).exists():
unique_slug = '{} {}'.format(t_slug, origin)
origin += 1
self.slug = unique_slug
super().save(*args, **kwargs)
精彩评论