Django - Slugs - Key (slug)=() is duplicated - python

just started fooling around with Django and came across a link here on how to create slugs. I was told to perform the following changes to an existing model:
from django.template.defaultfilters import slugify
class Category(models.Model):
name = models.CharField(max_length=128, unique=True)
views = models.IntegerField(default=0)
likes = models.IntegerField(default=0)
slug = models.SlugField(unique=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Category, self).save(*args, **kwargs)
def __unicode__(self):
return self.name
This worked out pretty well until I tried to migrate the database using:
python manage.py makemigrations
The above asked for a default value so following the guide, I gave it ''. Then:
python manage.py migrate
The above returned "DETAIL: Key (slug)=() is duplicated."
I'm not entirely sure why this happened. Perhaps it's because I'm adding a new field that is unique and I can't populate it with ''? If so, what do I have to do in order to populate the database?

The documentation explains how to migrate in these circumstances. A quick summary:
create the field without unique=True
create a migration with a RunPython function that iterates through all Categories and calls save on them, which will populate the slug
create a final migration which sets unique=True.

Actually i found an easier way to override the admin filter lists, that fixed my error of duplicated fields, So you can filter the fields in the admin panel to get all the duplicated fields and rename them ... and then migrate .. and it will work after removing all the duplication..
first create custom_filter.py file and include this code in it(assuming that you have a field with name slug)
from django.contrib.admin import SimpleListFilter
class DuplicatSlugFilter(SimpleListFilter):
"""
This filter is being used in django admin panel.
"""
title = "Duplicates"
parameter_name = "slug"
def lookups(self, request, model_admin):
return (("duplicates", "Duplicates"),)
def queryset(self, request, queryset):
if not self.value():
return queryset
if self.value().lower() == "duplicates":
return queryset.filter().exclude(
id__in=[slug.id for slug in queryset.distinct("slug").order_by("slug")]
)
Then add those lines to the admin.py file:
from .custom_filter import DuplicatSlugFilter
class CategoryAdmin(admin.ModelAdmin):
list_filter = (DuplicatSlugFilter,)
admin.site.register(Category, CategoryAdmin)
Good Luck

Related

Cannot modify a model field on save()

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)

Unable to autopopulate a Django admin field using an attribute from a OneToOne relationship

There are similar questions to this, but I believe mine is different. I am very new to Django and to Python, so please forgive my ignorance.
I have a custom class UserProfile that inherits from the django.contrib.auth.models User class. This UserProfile is based on the exercise in Tango with Django, however, I am using the example to create a different project/app.
I have UserProfile linked to the standard User model with a OneToOneField relationship in my models.py, as shown below:
class UserProfile(models.Model):
# Links UserProfile to a User model instance.
user = models.OneToOneField(User)
# The additional attribute I wish to include that isn't in User.
slug = models.SlugField(unique=True)
In my admin.py file, I want an interface for UserProfile that I can work with, and I want the slugfield to autopopulate when I enter a new UserProfile. I want it to autopopulate based on the username attribute of User. However, I can't seem to make it work. Here is my admin.py code:
class UserProfileAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("user.username",)}
When I try to runserver from my command line, I get the following error:
ERRORS: <class 'climbcast.admin.UserProfileAdmin'>: (admin.E030) The
value of >'prepopula ted_fields["slug"][0]' refers to 'user.username',
which is not an attribute of >' climbcast.UserProfile'.
System check identified 1 issue (0 silenced).
It won't allow me to access the user.username attribute this way, even though I can access it that way in the python shell. Any ideas on how to make this work?
Unfortunately prepopulated_fields doesn’t accept DateTimeField, ForeignKey, nor ManyToManyField fields.
Source: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.prepopulated_fields
Possible solution, in your models.py (make slug attribute optional):
from django.utils.encoding import force_text
from django.template.defaultfilters import slugify
class UserProfile(models.Model):
[...]
slug = models.SlugField(blank=True, db_index=True, unique=True)
def get_unique_slug(self, value):
"""
Generate a valid slug for a for given string.
"""
qs = self.__class__.objects.all()
used = qs.values_list('slug', flat=True)
i = 1
baseslug = slugify(value)
while slug in used:
slug = '%s-%s' % (baseslug, i)
i += 1
return slug
def save(self, *args, **kwargs):
if not self.slug and self.user:
self.slug = self.get_unique_slug(force_text(self.user.username))
super(UserProfile, self).save(*args, **kwargs)

IntegrityError: column slug is not unique

i'm studying django by tangowithdjango tutorials, and there on 7-th chapter I've stacked.
i'm trying to include a slug field in my model and override save method to make it all working. After migrate I have an integrity error.
models.py:
class Category(models.Model):
name = models.CharField(max_length=128, unique=True)
views = models.IntegerField(default=0)
likes = models.IntegerField(default=0)
slug = models.SlugField(unique=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Category, self).save(*args, **kwargs)
class Meta:
verbose_name_plural = "Categories"
def __unicode__(self):
return self.name
Thanks!
Listen to the error. You have the slug attribute specified as unique, but you must have the same slug value for multiple instances of your Category class.
This can easily happen if you add a unique attribute column to a table that already has data, because any constant default you use will automatically break the unique constraint.
To make the migration...
You're going to need to first migrate to just add the slug-field, without specifying it as unqiue, then set a unique slug for each Category in the database, then you can add the unique constraint and migrate again.
Open your most recent migration in the migrations directory of your app and edit the line that looks similar to
migrations.AddField(
model_name='category',
name='slug',
field=models.SlugField(unique=True),
preserve_default=False,
),
so that it is just
migrations.AddField(
model_name='category',
name='slug',
field=models.SlugField(null=True, blank=True),
preserve_default=False,
),
Then run the migration with python manage.py migrate (if you're using django 1.7. Otherwise use South for migrations), then open the django shell with python manage.py shell, import your Category model and run
for cat in Category.objects.all():
cat.slug = cat.name
cat.save()
This will set the slug for each category to be the category's name. Now, assuming all names are unique, you can create your migrations again and successfully migrate so that the DB will force unique slugs.

How to alphabetically order a drop-down list in Django admin?

Here's a (very) simplified version of my models:
laboratory/models.py
class Lab(Model):
professor = ForeignKey('authors.Author')
authors/models.py
class Author(Model):
name = CharField(max_length=100)
In the Django admin, when I add or update a Lab, a drop-down list containing each professors is automatically generated and displayed. The problem is this list is very long and it is not alphabetically ordered. I want the professor drop-down list to be alphabetically ordered by "name" field.
How can I do that?
You can define default ordering for Author model:
class Author(Model):
name = CharField(max_length=100)
class Meta:
ordering = ('name',)
Keep in mind that this causes the objects in Django also to ordered and migration will have to be done.
You can do ordering = ['name'] under the AuthorAdmin file to order only for admin dashboard.
ModelAdmin specific ordering via formfield_for_foreignkey
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "author":
kwargs["queryset"] = Author.objects.filter(anyfilters=anyfilters).order_by('name')
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
Note IMHO its better not to set the ordering on model because the ordering of your admin page needs to be decoupled from the model.
Also all the queries fired on the model will use the order_by column, in which case you might have to index the ordering column with your other columns.
The current way to do this (January 2019):
In your admin.py file:
class AuthorAdmin(admin.ModelAdmin):
ordering = ['name']
And then register it:
admin.site.register(Author, AuthorAdmin)
As described in the docs:
https://docs.djangoproject.com/en/2.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.ordering
Well, you can use the ordering variable in django admin, or you could use order_with_respect_to underneath class Meta. So, first you need to add an admin.py file to the same directory as your models.py file. This is what your admin.py file should look like:
from django.contrib import admin
from models import Lab
class LabAdmin(admin.ModelAdmin):
fields = ('name',)
class Meta:
ordering = ['name'] # ['-name'] if you want the opposite ordering
admin.site.register(Lab, LabAdmin)

Don't want show all items of a model object in django

class TaskManager(models.Manager):
def get_query_set(self):
return super(TaskManager, self).get_query_set().filter(Owner='jim')
class Task(models.Model):
Name = models.CharField('Title', max_length=200)
Notes = models.TextField('Description',max_length=2000, null=True)
project = models.ForeignKey(Project,null=True, blank=True)
Owner = models.CharField(max_length=100, choices=owner_set)
objects = TaskManager()
def __unicode__(self):
return self.Name
I have two models in my models.py, every Task has a project. But when i enter the Task page, it will list all the task by default. So i want it to list only the current user's Task by default. It is that the Task.Owner=current user.
Who can help me with this, thanks!
I have searched from the net and then get the solution:
I update the code i pasted just now above.
Thanks for all.
Why not do this!
Task.objects.filter(owner = current_user)
Update:
1) If you want to filter in you own custom template add this to your view code
2) If you are trying to customize the admin site do this,
from django.contrib import admin
from models import MyModel
class TaskAdmin(admin.ModelAdmin):
def queryset(self, request):
return super(TaskAdmin, self).queryset(request).filter(owner = request.user)
admin.site.register(Task, TaskAdmin)
In the ModelAdmin class you should add:
class TaskAdmin(...):
def queryset(self, request):
qs = super(TaskAdmin, self).queryset(request)
return qs.filter(Owner=request.user)
This will override the default queryset
more here:
https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.queryset
I just want to modify the default filter of a model, make the model list not all items without the modelAdmin, because the modelAdmin just provide a filter which need manual click, i want to filter the model by default automaticlly

Categories