5

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)
jayt
  • 758
  • 1
  • 14
  • 32

3 Answers3

13

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)
Jeremy S.
  • 1,086
  • 8
  • 18
2

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()
Tomasz Jakub Rup
  • 10,502
  • 7
  • 48
  • 49
1

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)
Sebastian Wozny
  • 16,943
  • 7
  • 52
  • 69
  • This doesn't return any errors and the object does successfully save when I submit the form - but it doesn't seem to produce a random slug when I look at the object in the admin page. Could I be missing something? – jayt Feb 24 '17 at 02:51
  • can you show the content of `models.py` with your save method( exact contents) – Sebastian Wozny Feb 24 '17 at 03:08
  • There was a mistake with a missing `str` it could not have possibly worked. You are not using the code yet. – Sebastian Wozny Feb 24 '17 at 03:15
  • Added in my save method for the model in Edit 2. – jayt Feb 24 '17 at 03:25
  • Apologies I put your code in my views.py instead of models.py. Once I put it in models.py it gives me an error when I attempt to save the form. Being `TypeError at /dashboard/campaign/new/ super() takes at least 1 argument (0 given)` – jayt Feb 24 '17 at 03:33
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/136571/discussion-between-jayt-and-sebastian-wozny). – jayt Feb 24 '17 at 17:29