I have a Model called ExampleModel
in Django, and want each of the model objects to be uniquely identified. Though, I don't want the ID of the object visible to the user in the URL; and so for this reason I want the objects slug
to be a unique, randomly generated integer with 8 digits which will go in the views URL. This is different from other questions I've seen because this means not producing a slug string that is based on the model object's name//content itself.
Models.py:
class ExampleModel(models.Model):
user = models.ForeignKey(UserModel, related_name='examplemodel', on_delete=models.CASCADE, null=True)
title = models.CharField(max_length=50, verbose_name='Title')
slug = models.SlugField(unique=True, blank=True, null=True)
Currently the value of the slug is null so I don't have to set a default slug for all of the current ExampleModel
objects.
This is quite vague understandably, however I haven't been able to find any guides/tutorials that may work for my exact situation.
Thanks for any help/guidance provided
Edit Here's my views.py:
def model_create(request):
user=request.user.id
if request.user.is_authenticated:
try:
example = request.user.examplemodel
except ExampleProfile.DoesNotExist:
example = ExampleProfile(user)
if request.method == 'POST':
form = NewForm(request.POST, request.FILES)
if form.is_valid():
form.save()
return redirect('/dashboard/')
else:
return render(request, 'create.html', {'form': form})
else:
form = NewForm()
return render(request, 'create.html', {'form': form})
else:
return redirect('/users/login/?next=')
Edit 2 Models.py (Save method):
def save(self, *args, **kwargs):
if self.user is None: # Set default reference
self.user = UserModel.objects.get(id=1)
super(ExampleModel, self).save(*args, **kwargs)
For creating unique slug in Django, we will be using Slugify and Django Signals. But first we will create a function for Creating a Slug using first_name and if that first_name is present in slug field, we will attach unique string to slug.
SlugField in Django is like a CharField, where you can specify max_length attribute also. If max_length is not specified, Django will use a default length of 50. It also implies setting Field.
Now in the post_detail() function in the views.py file, we can accept the captured slug from the URL using post_detail(request, slug). From there, we then use the Django ORM to query the database using that slug with the Post. objects. get(slug=slug) code.
Django has a get_random_string
function built in that can generate the random string needed for your slug.
As Sebastian Wozny mentions, you want to call this as you override the save method. The basics are:
from django.utils.crypto import get_random_string
# ...
the_slug = get_random_string(8,'0123456789') # 8 characters, only digits.
That's not actual working code. In more detail a real models.py would look like the below. Note that I haven't limited myself to digits and I'm doing a checks both for unqueness and to make sure it doesn't spell anythig bad:
from django.db import models
from django.utils.crypto import get_random_string
# ...
class SomeModelWithSlug(models.Model):
slug = models.SlugField(max_length=5,blank=True,) # blank if it needs to be migrated to a model that didn't already have this
# ...
def save(self, *args, **kwargs):
""" Add Slug creating/checking to save method. """
slug_save(self) # call slug_save, listed below
Super(SomeModelWithSlug, self).save(*args, **kwargs)
# ...
def slug_save(obj):
""" A function to generate a 5 character slug and see if it has been used and contains naughty words."""
if not obj.slug: # if there isn't a slug
obj.slug = get_random_string(5) # create one
slug_is_wrong = True
while slug_is_wrong: # keep checking until we have a valid slug
slug_is_wrong = False
other_objs_with_slug = type(obj).objects.filter(slug=obj.slug)
if len(other_objs_with_slug) > 0:
# if any other objects have current slug
slug_is_wrong = True
naughty_words = list_of_swear_words_brand_names_etc
if obj.slug in naughty_words:
slug_is_wrong = True
if slug_is_wrong:
# create another slug and check it again
obj.slug = get_random_string(5)
Override save:
def save(self, *args, **kwargs):
try:
self.slug = ''.join(str(random.randint(0, 9)) for _ in range(8))
super().save(*args, **kwargs)
except IntegrityError:
self.save(*args, **kwargs)
This might need some more safeguards against IntegrityError
s though.
If you can live with two saves:
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
try:
self.slug = ''.join(str(random.randint(0, 9)) for _ in range(8))
super().save(*args, **kwargs)
except IntegrityError:
self.save(*args, **kwargs)
If you override the save method, every time the object updates slug changes, If you don't want that then doing it like this only sets the slug the first time:
def slug_generator():
return ''.join(random.choices(string.ascii_lowercase + string.digits + string.ascii_uppercase, k=20))
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slug_generator()
super(Item, self).save()
super(Item, self).save()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With