Process a topic name for basic forum app

Hi everyone !

I’m imporving my Django skills through a basic forum app.

Here are my templates files :

  1. forums.html (Will show all of the Forum models)

  2. topics.html (Will show all of the Topic models according to the Forum ForeignKey)

  3. posts.html (Will show all of the related *post to a Topic)

I’ve got one issue in the topics.html template.
Indeed :

<tbody>

                {% for topic in topic_model %}

                <tr>
                    <td><a href="{{topic.topic_name}}">{{topic.topic_name}}</a></td>
                    <td>{{topic.topic_description}}</td>
                    <td>{{topic.topic_author}}</td>
                    <td>{{topic.topic_answers}}</td>
                    <td>{{topic.topic_last_answer_by}}</td>
                </tr>

                {% endfor %}

</tbody>

urls.py

from django.urls import path
from . import views

urlpatterns = [
    path("", views.index, name="index"),
    path("<str:forum_name>/", views.forum_page, name="forum_page"),
    path("topics/<str:topic_name>/", views.topic_page, name="topic_page")
]

My issue is that assuming we have a topic named “Hello World”, with the configuration above the URL will be :

localhost:8000/forums/topics/Hello World

and I would like to convert it as :

localhost:8000/forums/topics/hello-world

How can I process to implement the following function inside my template ? :

def processTopicName(topic_model):

    #Process topic name

    new_topic_model = []

    for topic in topic_model:

        topic_new_name = topic_name.replace(" ","-")
        topic_new_name = topic_new_name.lower()

        new_topic_model.append(topic_new_name)

    return topic_new_name 

Thank you in advance for your help

Perhaps you’re looking for the slugify feature?

Also, in django.utils.text there’s the slugify function.

Both of which will eventually lead you to reading SlugField and https://docs.djangoproject.com/en/3.0/ref/urls/#module-django.urls.conf

I suggest using the AutoSlugField from the django-extensions package. It will do everything for you automatically, check docs here: https://django-extensions.readthedocs.io/en/latest/field_extensions.html#current-database-model-field-extensions

1 Like

Hi guys ! Thanks ! Your solution did work !

Thus, I have one other request. How could I simplify my code ? Because I’d like to create a base class “Slugs” like so :

class Slugs():
    model_name_raw = models.CharField(max_length=40, default=None)
    model_name_slug = models.SlugField(max_length=80, default=None, blank=True)

    def save(self, *args, **kwargs):
        self.model_name_slug = slugify(self.model_name_raw)
        self.save(*args, **kwargs)

    def __str__(self):
        return self.model_name_raw

And then implement it via inheritance like :

class Forum(models.Model, Slugs):
    forum_description = models.CharField(max_length=100)

class Topic(models.Model, Slugs):
    topic_forum = models.ForeignKey(Forum, related_name="topic_forum", on_delete=models.CASCADE)
    topic_description = models.CharField(max_length=100)
    topic_author = models.ForeignKey(User, related_name="topic_author", on_delete=models.CASCADE)
    topic_answers = models.IntegerField(default=0)
    topic_last_answer_by = models.ForeignKey(User,related_name="topic_last_answer_by",on_delete=models.CASCADE)

Thank you in advance for your help

I think you need an abstract class here. And the save method should call a super. So I would change Slugs to:

class ModelWithSlugs(models.Model):
    model_name_raw = models.CharField(max_length=40, default=None)
    model_name_slug = models.SlugField(max_length=80, default=None, blank=True)

    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        self.model_name_slug = slugify(self.model_name_raw)
        super().save(*args, **kwargs)

    def __str__(self):
        return self.model_name_raw

And then in your code:

class Forum(ModelWithSlugs):
    ...

class Topic(ModelWithSlugs):
   ....

Something like that…

Hi @timonweb !

Thank you for your answer, but it doesn’t really work (well, it is just that I don’t know how to resolve the following issue). When I try to

python manage.py makemigrations

I obtain :

You are trying to add a non-nullable field 'slugs_ptr' to forum without a default; we can't do that (the database needs something to populate existing rows)
Please select a fix: 
1) Provide a one-off default now (will be set on all existing rows with a null value for this column) 
2) Quit, and let me add a default in models.py

Here is my code updated :

class Slugs(models.Model):

    model_name_raw = models.CharField(max_length=40, default=None)

    model_name_slug = models.SlugField(max_length=80, default=None, blank=True)

    def save(self, *args, **kwargs):

        self.model_name_slug = slugify(self.model_name_raw)

        super().save(*args, **kwargs)

    def __str__(self):

        return self.model_name_raw

# Create your models here.

class Forum(Slugs):

    forum_description = models.CharField(max_length=100)

class Topic(Slugs):

    topic_forum = models.ForeignKey(Forum, related_name="topic_forum", on_delete=models.CASCADE)

    topic_description = models.CharField(max_length=100)

    topic_author = models.ForeignKey(User, related_name="topic_author", on_delete=models.CASCADE)

    topic_answers = models.IntegerField(default=0)

    topic_last_answer_by = models.ForeignKey(User,related_name="topic_last_answer_by",on_delete=models.CASCADE)

You missed the Meta definition of an abstract class that timonweb provided in his example of the Slugs class. Without it, it’s trying to create a table named Slugs along with an implicit foreign key field in the child classes. This is most likely not what you want.

    class Meta:
        abstract = True

Hi @KenWhitesell, thank you for your answer :slight_smile:

Well it works now ! But is it a common Python thing to put an inside :

class Meta:
    abstract = True

Or a Django related thing ?

It’s a Django related thing. By default, Django ORM assumes a database table for every class that extends models.Model, but if you don’t want this behaviour and want to use a class as a base without a table, you add abstract = True to its meta. More info here: https://docs.djangoproject.com/en/3.0/topics/db/models/#abstract-base-classes

1 Like

In addition to @timonweb above, I’d also recommend looking at the rest of the options available in the Meta class available for models. (Also see Model Meta options.)