459π
You will need to use the slugify function.
>>> from django.template.defaultfilters import slugify
>>> slugify("b b b b")
u'b-b-b-b'
>>>
You can call slugify
automatically by overriding the save
method:
class Test(models.Model):
q = models.CharField(max_length=30)
s = models.SlugField()
def save(self, *args, **kwargs):
self.s = slugify(self.q)
super(Test, self).save(*args, **kwargs)
Be aware that the above will cause your URL to change when the q
field is edited, which can cause broken links. It may be preferable to generate the slug only once when you create a new object:
class Test(models.Model):
q = models.CharField(max_length=30)
s = models.SlugField()
def save(self, *args, **kwargs):
if not self.id:
# Newly created object, so set slug
self.s = slugify(self.q)
super(Test, self).save(*args, **kwargs)
113π
There is corner case with some utf-8 characters
Example:
>>> from django.template.defaultfilters import slugify
>>> slugify(u"test Δ
ΔΕΔΓ³ΕΕ")
u'test-aescon' # there is no "l"
This can be solved with Unidecode
>>> from unidecode import unidecode
>>> from django.template.defaultfilters import slugify
>>> slugify(unidecode(u"test Δ
ΔΕΔΓ³ΕΕ"))
u'test-aescoln'
- [Django]-How to update an existing Conda environment with a .yml file
- [Django]-Django models.py Circular Foreign Key
- [Django]-What is the difference between null=True and blank=True in Django?
71π
A small correction to Thepeerβs answer: To override save()
function in model classes, better add arguments to it:
from django.utils.text import slugify
def save(self, *args, **kwargs):
if not self.id:
self.s = slugify(self.q)
super(test, self).save(*args, **kwargs)
Otherwise, test.objects.create(q="blah blah blah")
will result in a force_insert
error (unexpected argument).
- [Django]-Why am I getting this error in Django?
- [Django]-UUID as default value in Django model
- [Django]-How can I handle Exceptions raised by dango-social-auth?
31π
If youβre using the admin interface to add new items of your model, you can set up a ModelAdmin
in your admin.py
and utilize prepopulated_fields
to automate entering of a slug:
class ClientAdmin(admin.ModelAdmin):
prepopulated_fields = {'slug': ('name',)}
admin.site.register(Client, ClientAdmin)
Here, when the user enters a value in the admin form for the name
field, the slug
will be automatically populated with the correct slugified name
.
- [Django]-Find object in list that has attribute equal to some value (that meets any condition)
- [Django]-How to format time in django-rest-framework's serializer?
- [Django]-How to server HTTP/2 Protocol with django
22π
In most cases the slug should not change, so you really only want to calculate it on first save:
class Test(models.Model):
q = models.CharField(max_length=30)
s = models.SlugField(editable=False) # hide from admin
def save(self):
if not self.id:
self.s = slugify(self.q)
super(Test, self).save()
- [Django]-Constructing Django filter queries dynamically with args and kwargs
- [Django]-What's the difference between select_related and prefetch_related in Django ORM?
- [Django]-Remove Labels in a Django Crispy Forms
6π
If you donβt want to set the slugfield to Not be editable, then I believe youβll want to set the Null and Blank properties to False. Otherwise youβll get an error when trying to save in Admin.
So a modification to the above example would be::
class test(models.Model):
q = models.CharField(max_length=30)
s = models.SlugField(null=True, blank=True) # Allow blank submission in admin.
def save(self):
if not self.id:
self.s = slugify(self.q)
super(test, self).save()
- [Django]-Uwsgi installation error in windows 7
- [Django]-Django gives Bad Request (400) when DEBUG = False
- [Django]-Combining Django F, Value and a dict to annotate a queryset
6π
Use prepopulated_fields
in your admin class:
class ArticleAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("title",)}
admin.site.register(Article, ArticleAdmin)
- [Django]-Django: Get list of model fields?
- [Django]-Equivalent of PHP "echo something; exit();" with Python/Django?
- [Django]-How do I clone a Django model instance object and save it to the database?
4π
Iβm using Django 1.7
Create a SlugField in your model like this:
slug = models.SlugField()
Then in admin.py
define prepopulated_fields
;
class ArticleAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("title",)}
- [Django]-Laravel's dd() equivalent in django
- [Django]-How to revert the last migration?
- [Django]-Django model one foreign key to many tables
0π
You can look at the docs for the SlugField
to get to know more about it in more descriptive way.
- [Django]-Django render_to_string missing information
- [Django]-Why am I getting this error in Django?
- [Django]-How to format time in django-rest-framework's serializer?
0π
You can create slug
field with SlugField() and slugify a name
field value with slugify(), then save the slugified name
field value as a slug
field value in overridden save() every time you add and change a Product
object as shown below:
# "models.py"
from django.utils.text import slugify
class Product(models.Model):
name = models.CharField(max_length=20)
slug = models.SlugField()
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super().save(*args, **kwargs)
And, you can prevent broken links by using id
in addition to slug
in the url as shown below:
# β "slug" β
http://myshop.com/product/orange-juice/id
# "id" β
So, the url takes id
in addition to slug
in urls.py
as shown below:
# "urls.py"
urlpatterns = [
path( # β "slug" βγ
'product/<slug:slug>/<int:id>/',
views.get_product, # β "id" β
name="get_product"
)
]
Then, you should treat only id
not treating slug
in get_product()
in views.py
as shown below:
# "views.py"
def get_product(request, id=None, slug=None):
product_obj = None
if id:
product_obj = Product.objects.get(id=id)
return HttpResponse(product_obj)
- [Django]-Python (and Django) best import practices
- [Django]-What's the difference between select_related and prefetch_related in Django ORM?
- [Django]-When saving, how can you check if a field has changed?
0π
In some cases slugify isnβt enough standalone. Iβm using a different technique for slugs. I think this will cover most scenarios.
First create a slug.py file in your app directory and copy the following codes in it.
slug.py
import re
from django.template.defaultfilters import slugify
def unique_slugify(instance, value, slug_field_name='slug', queryset=None,
slug_separator='-'):
"""
Calculates and stores a unique slug of ``value`` for an instance.
``slug_field_name`` should be a string matching the name of the field to
store the slug in (and the field to check against for uniqueness).
``queryset`` usually doesn't need to be explicitly provided - it'll default
to using the ``.all()`` queryset from the model's default manager.
"""
slug_field = instance._meta.get_field(slug_field_name)
slug = getattr(instance, slug_field.attname)
slug_len = slug_field.max_length
# Sort out the initial slug, limiting its length if necessary.
slug = slugify(value)
if slug_len:
slug = slug[:slug_len]
slug = _slug_strip(slug, slug_separator)
original_slug = slug
# Create the queryset if one wasn't explicitly provided and exclude the
# current instance from the queryset.
if queryset is None:
queryset = instance.__class__._default_manager.all()
if instance.pk:
queryset = queryset.exclude(pk=instance.pk)
# Find a unique slug. If one matches, at '-2' to the end and try again
# (then '-3', etc).
next = 2
while not slug or queryset.filter(**{slug_field_name: slug}):
slug = original_slug
end = '%s%s' % (slug_separator, next)
if slug_len and len(slug) + len(end) > slug_len:
slug = slug[:slug_len-len(end)]
slug = _slug_strip(slug, slug_separator)
slug = '%s%s' % (slug, end)
next += 1
setattr(instance, slug_field.attname, slug)
def _slug_strip(value, separator='-'):
"""
Cleans up a slug by removing slug separator characters that occur at the
beginning or end of a slug.
If an alternate separator is used, it will also replace any instances of
the default '-' separator with the new separator.
"""
separator = separator or ''
if separator == '-' or not separator:
re_sep = '-'
else:
re_sep = '(?:-|%s)' % re.escape(separator)
# Remove multiple instances and if an alternate separator is provided,
# replace the default '-' separator.
if separator != re_sep:
value = re.sub('%s+' % re_sep, separator, value)
# Remove separator from the beginning and end of the slug.
if separator:
if separator != '-':
re_sep = re.escape(separator)
value = re.sub(r'^%s+|%s+$' % (re_sep, re_sep), '', value)
return value
Then in your models.py import unique_slugify from that file. Then use it in save() method.
models.py
from django.db import models
from yourapp.slug import unique_slugify # Change "yourapp" with your app name
class Test(models.Model):
q = models.CharField(max_length=30)
s = models.SlugField()
def save(self, **kwargs):
slug = '%s' % (self.q)
unique_slugify(self, slug)
super(Test, self).save()
- [Django]-Django template can't see CSS files
- [Django]-Django ignores router when running tests?
- [Django]-How to get an ImageField URL within a template?