I've encountered a problem with Django's built-in slugify function. I'm building a website using Django framework. The site must have a forum app. After a bit of searching, I've found one. It works great, however, it's using the slugify function heavily on the topic titles to create "human readable" links to its pages. The problem is, we are writing in Russian, so as the result, it generates non-ASCII URLs which look like an unreadable mess of unicode data when trying to copy the link from the browser (and also throws an exception when trying to log them).
Is there a way to override the Django's django.utils.text.slugify globally for the whole project so I don't need to include half of the third party library only to change the import statements in their models.py ?
One way where it is not global is to write your own slugify function and then you can utilise that by calling it in overridden save method of the model where you want to slugify the title/name field.
Eg.
class Post(models.Model):
title = models.CharField(max_length=512)
slug = models.CharField(max_length=1024)
def save(self, *args, **kwargs):
your_slugify_function(self, self.title)
super(Post, self).save(*args, **kwargs)
I think you can define a abstract model class with overriding the save method with slugify function. Like this:
class AbstractBase(models.Model):
slug = models.SlugField()
class Meta:
abstract = True
def save(self, *args, **kwargs):
self.slug = slugify.Slugify(self.slug)
return super(AbstractBase, self).save(*args, **kwargs)
And sub-class your rest of the models from this abstract class like:
class Post(AbstractBase):
# rest of the post fields
In this way, slugify will be done only in one place and work globally across all models.
Related
I am trying to automatically generate slugs to use in URLs of a Django app.
So far I have achieved this using prepopulated_fields in admin.py.
However, slugs generated with prepopulated_fields do not include stop words (i.e., the string "I love to code" has slug "love-code").
Is there a way to automatically generate slugs that also include stop words?
Slug is generated on fronted using prepopulate.js function and does not have any configuration options, you could add your custom javascript instead
Or
forget about preopopulated_fields and instead override save method on Headword model and generate slug using slugify() something in a line of
from django.utils.text import slugify
class Headword(models.Model):
...
def save(self, *args, **kwargs):
self.slug = slugify(self.headword, allow_unicode=True)
super().save(*args, **kwargs)
I'm a beginner with Django, and first time askig :)
I'm following a simple tutorial on generating a slug for a string (let's say a slug for a blog post generated from its title).
Perhaps I'm following an outdated guide, perhaps I'm missing a basic thing, I have no idea.
Django 2.0
Python 3.6
I am trying to do a very simple task of slugifying a simple string, so:
User enters a string in a simple form
When hitting 'save', the title goes through slugify and creates the
slug
Save.
models.py
from django.db import models
class Testmodel(models.Model):
title = models.CharField(max_length=220)
slug = models.SlugField(unique=True, null=True)
def __str__(self):
return self.title
views.py
from django.views.generic.edit import CreateView
class TestCreate(CreateView):
model = Testmodel
fields = '__all__'
forms.py
from django.forms import ModelForm
from .models import Testmodel
class TestCreateForm(ModelForm):
class Meta:
model = Testmodel
fields = '__all__'
Up until here everythig works, if I enter the slug manualy. In order to do it automaticaly, I have tried:
Overriding the save() method withing my ModelForm class.
Overriding the form_valid() method within the CreateView
Overriding the save() method within the model itself.
Tried to connect a pre_save signal to the model.
In all of these 4 tries, I had the same results:
When generating the form with the slug field, I couldn't do anything because it was required.
When generating the form without the slug field, nothing happens when I hit save.
The only way I have found to dodge this issue is to set the slug field to blank = True as well. I am not sure how secure it is, though?
Thank you!
Welcome to StackOverflow. You've written a wonderfuly constructed question (Cheers!)
When generating the form with the slug field, I couldn't do anything because it was required.
Okay so first we exlcude the slug because we want it to be autogenerated.
You can do this by
class TestCreateForm(ModelForm):
class Meta:
model = Testmodel
exclude = ['slug']
Now you'll get a form without the slug field.
When generating the form without the slug field, nothing happens when I hit save.
Now we override the save() function of the model itself since slug is a part of the model.
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super().save(*args, **kwargs)
But this will generate the slug everytime the model is saved.
We can go a step further and make sure the slug is set only if the model is 'created' and not every time it is 'updated'
def save(self, *args, **kwargs):
if not self.id:
self.slug = slugify(self.title)
super().save(*args, **kwargs)
I have a database of articles that are reused across multiple Django sites. There is also some site-specific information for each Article, stored in the SiteArticle model. One bit of information is a list of site-specific tags for each SiteArticle. Here's the models.py:
class Article(models.Model):
sites = models.ManyToManyField(Site, through='SiteArticle')
class SiteArticle(models.Model):
site = models.ForeignKey(Site)
article = models.ForeignKey(Entiteit)
tags = models.ManyToManyField('Tag', blank=True)
class Tag(models.Model):
name = models.CharField(max_length=255)
site = models.ForeignKey(Site, related_name='tags')
I use an inline admin to edit and add the SiteArticle objects of each Article. Here's admin.py:
class InlineSiteArticle(admin.StackedInline):
model = SiteArticle
#admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
inlines = [InlineSiteArticle]
When editing an article, I would like the inline SiteArticle forms to only display the tags of the relevant site. I tried overriding the formfield_for_manytomany() method, but here I don't have access to instance variable (which should be an instance of the current SiteArticle) that I need to filter the queryset:
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == "tags":
kwargs["queryset"] = instance.site.tags.all()
^^^^^^^^
return super(InlineSiteArticle, self).formfield_for_manytomany(db_field, request, **kwargs)
I already looked at this Stack Overflow answer which solves a very related problem. In my case, however, I do not need access to the "parent" instance, but simply to the instance of the current form's SiteArticle object. How can I solve this?
EDIT -- I already figured out that get_formset() does get an instance passed in. However, this instance is an Article, not the SiteArticle needed to filter the queryset.
Here's the answer I figured out myself. Feel free to edit, comment, or provide a better solution!
I created a custom form for editing SiteArticles, which I passed to the ArticleAdmin using the form option of ModelAdmin. In the constructor of this form I performed the filtering of the queryset based on the current model instance, which is now available as self.instance.
class CustomSiteArticleForm(forms.ModelForm):
class Meta:
model = SiteArticle
fields = '__all__'
def __init__(self, *args, **kwargs):
super(CustomSiteArticleForm, self).__init__(*args, **kwargs)
if self.instance.pk:
self.fields['tags'].queryset = self.instance.site.tags.all()
else:
self.fields['tags'].queryset = Tag.objects.none()
this question might get a bit large,i will try to explain perrty much everything whats going on.below is my heading model which fills the slug field itself by whatever is the title:
class Heading(models.Model):
category = models.ForeignKey(Category)
title = models.CharField(max_length=5000)
content =RichTextUploadingField ()
image= models.ImageField(null=True,blank=True)
date = models.DateField(default=datetime.now())
time = models.TimeField(default=datetime.now())
slug = models.SlugField(unique=True, null=True, blank=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super(Heading, self).save(*args, **kwargs)
my title is in a foreign language(nepali language to be specific)
below is the image of my admin panel to fill up the heading class
as you can see my title is in foreign language but my slug field is filled automatically by converting that title in english language which is not what i want,i want my slug field to be filled in the same language as my title field.i did some search and a module called unidecode might be the solution to it,i tried using it too but since its documentation is not well i couldn't get hook of it.so if there is any other solution or how to make proper use of unidecode?any kind of help or clue would be greatly appreciated
The problem is, slugification happens in JavaScript (at least in the standard Django admin), before it reaches the server (you can look up urlify.js in the admin contrib package).
There's a new option on SlugField called allow_unicode, which might do what you want, but it's been introduced in Django 1.9.
If you cannot upgrade to 1.9 yet, you could in theory set up some endpoint on your server that would take a string, run unidecode on it, and return it, and then cook up some custom JavaScript code that would override the default slugification in the admin, but that sounds like quite a lot of work.
Another option would be to hide the slug field from the admin altogether, and do something similar to that snippet of code you posted in your question, except you should probably do this in the ModelAdmin class instead of the model itself (and you probably want to use unidecode there before passing the string to slugify).
Firstly you have to import "SLUGIFY" [from django.utils.text import slugify].
Secondly, In model,
slug = models.SlugField(allow_unicode=True, unique=True, null=True,
blank=True)
After that:
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
if not self.slug:
slug_str = f"{self.title}"
self.slug = slugify(slug_str, allow_unicode=True)
super(Blog, self).save(*args, **kwargs)
Finally Don't forget to add
[ALLOW_UNICODE_SLUGS = True]
in settings.py.
I am using Python 2.7 and Django 1.6.3
I want to define extra model field which is not actually in db table. I have a way which is defining a callable method with property annotation like;
class MyClass(models.Model):
my_field = models.CharField(max_length=50)
#property
def my_extra_field(self):
return self.my_field+'extra value'
This works fine to show it on admin change list pages. But the extra field is not on db level. It is being generated on programming level. Django asks it for every model object.
This cause me some troubles. My all admin change list pages have capability of exporting as excel or some other type. I am using admin query set to build that report. I have also jasper reports mechanism that works with SQL select queries. So, I, want to use the queryset to take this select query.
I think being able to define extra fields on db level is important for something. Not just for reason of mine. So, the question all about this.
Is there a way to define an extra custom fields on db level instead of programming level in Django.
Thank you!.
Edited
Adding it to admin list_filter is also another problem if it is not really a field. Django does not allow you to add it.
Could you create a new database field and then overwrite the save method to populate that field? I do that often to create a marked up version of a text field. For example:
class Dummmy(models.Model):
content = models.TextField()
content_html = models.TextField(editable=False, null=True)
def save(self, *args, **kwargs):
self.content_html = markdown(self.content)
super(Dummmy, self).save(*args, **kwargs)
So for you:
class MyClass(models.Model):
my_field = models.CharField(max_length=50)
my_extra_field = models.CharField(editable=False, null=True)
def save(self, *args, **kwargs):
self.my_extra_field = self.my_field + 'extra value'
super(MyClass, self).save(*args, **kwargs)