Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django - Generating random, unique slug field for each model object

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)
like image 857
jayt Avatar asked Feb 24 '17 02:02

jayt


People also ask

How do you make unique slugs in Django?

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.

How does Django define slug field?

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.

How do I get Slugs in Django?

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.


3 Answers

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)
like image 152
Jeremy S. Avatar answered Oct 19 '22 22:10

Jeremy S.


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 IntegrityErrors 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)
like image 24
Sebastian Wozny Avatar answered Oct 20 '22 00:10

Sebastian Wozny


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()
like image 20
Farhood Vatanzadeh Avatar answered Oct 19 '22 22:10

Farhood Vatanzadeh