Django unique slug field for two or more models - python

I have such structure:
class Category(models.Model):
name = models.CharField(max_length=255, validators=[MinLengthValidator(3)])
parent = models.ForeignKey('self', blank=True, null=True,
related_name='children',
on_delete=models.CASCADE
)
slug = models.SlugField(max_length=255, null=False, unique=True)
class Product(models.Model):
name = models.CharField(max_length=255, validators=[MinLengthValidator(3)])
to_category = models.ForeignKey(Category, on_delete=models.SET_NULL,
blank=True, null=True,
)
slug = models.SlugField(max_length=255, null=False, unique=True)
I have created one category with slug "test". When I try to create new category with slug "test" I got warning message and it is Ok. But If I try to create product with slug "test" I dont have warning and this is not good in my case. Is there a solution or method to validate slug field for uniqueness with Product and Category model?

You can override the save method for each, and then check if the given slug already exists for a product or category.
def is_slug_unique(slug):
product_exists = Product.objects.filter(slug=slug).exists()
category_exists = Category.objects.filter(slug=slug).exists()
if product_exists or category_exists:
return False
else:
return True
class Category(models.Model)
...
def save(self, *args, **kwargs):
slug_unique = is_slug_unique(self.slug)
if not slug_unique:
# do something when the slug is not unique
else:
# do something when the slug is unique
super().save(*args, **kwargs)
class Product(models.Model)
...
def save(self, *args, **kwargs):
slug_unique = is_slug_unique(self.slug)
if not slug_unique:
# do something when the slug is not unique
else:
# do something when the slug is unique
super().save(*args, **kwargs)

An idea might be to create a Slug model that stores all the slugs, optionally with a backreference to the object:
class Slug(models.Model):
slug = models.SlugField(max_length=255, primary_key=True)
Then the slugs in your models are ForeignKeys to that Slug model, and you check if such slug already exists:
from django.core.exceptions import ValidationError
class Product(models.Model):
name = models.CharField(max_length=255, validators=[MinLengthValidator(3)])
to_category = models.ForeignKey(
Category, on_delete=models.SET_NULL, blank=True, null=True
)
slug = models.ForeignKey(Slug, on_delete=models.PROTECT)
def validate_slug(self):
if self.pk is not None and Slug.objects.filter(pk=self.slug_id).exclude(
product__pk=self.pk
):
raise ValidationError('The slug is already used.')
def clean(self, *args, **kwargs):
self.validate_slug()
return super().clean(*args, **kwargs)
def save(self, *args, **kwargs):
self.validate_slug()
return super().save(*args, **kwargs)
That being said, often overlapping slugs for different entity types are allowed.

Related

Django override save method with changing field value

I need some help with overriding save method with changing field value.
I have such structure:
models.py
class Category(models.Model):
name = models.CharField(max_length=255, validators=[MinLengthValidator(3)])
parent = models.ForeignKey('self', blank=True, null=True,
related_name='children',
on_delete=models.CASCADE
)
class Product(models.Model):
name = models.CharField(max_length=255, validators=[MinLengthValidator(3)])
to_category = models.ForeignKey(Category, on_delete=models.SET_NULL,
blank=True, null=True,
)
to_categories = models.ManyToManyField(Category, blank=True,
related_name='categories',
)
def save(self, *args, **kwargs):
super(Product, self).save(*args, **kwargs)
So I can't find a correct sollution for save method. I can select category on the "to_category" and categories on "to_categories" field, but I need if I selected one of the categories on the "to_category" field then save the Product, this selected field must be automatically selected on the "to_categories" field.
I found a sollution.
admin.py:
class ProductAdmin(admin.ModelAdmin):
...
def save_related(self, request, form, formsets, change):
super(ProductAdmin, self).save_related(request, form, formsets, change)
category = Category.objects.get(id=form.instance.to_category.id)
form.instance.to_categories.add(category)

How to auto add a field in django admin model calculating age

is it possible to add an age field that is auto filled in the runtime based on another date of birth field at the django admin interface, i added a screenshot trying to explain more what i mean
my models.py
class FamilyMember(models.Model):
transaction = models.ForeignKey(Transaction, on_delete=models.CASCADE)
family_group = models.ForeignKey(FamilyGroup,
on_delete=models.CASCADE,
null=True,
blank=True)
name = models.CharField(max_length=100, null=True, blank=True)
date_of_birth = models.DateField(null=True, blank=True)
relationship = models.ForeignKey(Relationship, on_delete=models.PROTECT)
dependant_child_age_range = models.ForeignKey(DependantChildAgeRange,
null=True,
blank=True,
on_delete=models.PROTECT)
care_percentage = models.PositiveSmallIntegerField(
null=True, blank=True, validators=[
MaxValueValidator(100),
])
income = models.DecimalField(max_digits=6,
decimal_places=2,
null=True,
blank=True)
rent_percentage = models.PositiveSmallIntegerField(
null=True, blank=True, validators=[
MaxValueValidator(100),
])
admin.py
class FamilyMemberInline(admin.TabularInline):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
action = request.META['PATH_INFO'].strip('/').split('/')[-1]
if action == 'change':
transaction_id = request.META['PATH_INFO'].strip('/').split('/')[-2]
if db_field.name == "family_group":
kwargs["queryset"] = FamilyGroup.objects.filter(transaction=transaction_id)
return super(FamilyMemberInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
model = FamilyMember
extra = 0
def sufficient_info_provided (self, obj):
return obj.sufficient_information_provided
sufficient_info_provided.boolean = True
readonly_fields = ['sufficient_info_provided',]
Override your inline's get_queryset method to annotate the queryset with the calculation. The annotation will add an age attribute to each object in the queryset.
Then as you can see in the ModelAdmin.list_display documentation, you can include a string representing a ModelAdmin method that accepts one argument, the model instance. Inline's work in the same way but you must include the method in ModelAdmin.readonly_fields.
Putting it all together:
class FamilyMemberInline(admin.TabularInline):
...
fields = (..., 'get_age')
readonly_fields = ('get_age',)
def get_queryset(self, request):
return (
super().get_queryset(request)
.annotate(age=...)
)
def get_age(self, instance):
return instance.age

Django Admin model create new instance instead of update

I've multiple models in my Django project but only this given below model creating another instance on update instead of save. This is happening in Django's Admin panel, not on my custom UI. When I remove my save() method then it works fine but this way I won't be able to create slug.
Does anybody know what I'm doing wrong in here
class Course(models.Model):
title = models.CharField(max_length=200)
user = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name='category')
slug = models.SlugField(max_length=200, unique=True, primary_key=True, auto_created=False)
short_description = models.TextField(blank=False, max_length=60)
description = models.TextField(blank=False)
outcome = models.CharField(max_length=200)
requirements = models.CharField(max_length=200)
language = models.CharField(max_length=200)
price = models.FloatField(validators=[MinValueValidator(9.99)])
level = models.CharField(max_length=20)
application_link = models.URLField(max_length=1000, blank=True, null=True)
brochure = models.FileField(upload_to='brochures/', blank=True, null=True)
thumbnail = models.ImageField(upload_to='thumbnails/')
video_url = models.CharField(max_length=100)
is_session_available = models.BooleanField(default=False)
session_url = models.CharField(max_length=250, default='')
is_published = models.BooleanField(default=True)
created_at = models.DateTimeField(default=now)
updated_at = models.DateTimeField(default=now)
def __str__(self):
return self.title
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super(Course, self).save(*args, **kwargs)
instead of overriding save method you could do this:
admin.py
class CourseAdmin(admin.ModelAdmin):
prepopulated_fields = {'slug': ('title',)}
admin.site.register(Course, CourseAdmin)
It is happening because it will generate new slug every time. You can do something like this:
def generate_slug(self):
self.slug = slugify(self.name)
def save(self, *args, **kwargs):
if not self.slug:
self.generate_slug() # This will generate slug.
return super().save(*args, **kwargs)

Iterate Slugify In Django. For multiple objects named the same

I would like to be able to change the slug using slugify if the slug already exist. This site will have multiple products named the same but when you call the product using get_object_or_404 you will get an error because two or more objects are being called at one time. To avoid this I need to auto increment the slugify if the slug already exist.
Can anyone help me with this?
class Product(models.Model):
product_name = models.CharField(max_length=500, blank=True)
product_description = models.TextField(blank=True)
company = models.ForeignKey(Company, blank=True, null=True)
category = models.ForeignKey(Category, blank=True, null=True)
manufacturer = models.ForeignKey(Manufacturer)
buy_link = models.URLField(max_length=1000, blank=True)
product_image_url = models.URLField(max_length=1000, blank=True)
price = models.CharField(max_length=30, blank=True)
orginal_price = models.CharField(max_length=30, blank=True)
stock = models.CharField(max_length=30, blank=True)
sku = models.CharField(max_length=250, blank=True)
slug = models.SlugField(max_length=500)
date_added = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now_add=False, auto_now=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.product_name)
super(Product, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('products:product_detail', args=[self.slug]) #kwargs={'slug': self.slug}
def __str__(self):
return self.product_name
What if you combine both product name and id, something like:
def save(self, *args, **kwargs):
self.slug = slugify("{obj.product_name}-{obj.id}".format(obj=self))
super(Product, self).save(*args, **kwargs)
Or, you can generate a slug, check if it exists and, if does, increment and append the counter - sample here and here.
Also, you should probably enforce the slug uniqueness:
slug = models.SlugField(max_length=500, unique=True)
You can use django-autoslug if you don't want to roll your own. Simply install the package with pip (pip install django-autoslug), import into your model file, and add it as a field. When set up with unique=True, if django-autoslug runs into a collision, it will append a number to the end of the slug, and increment that number if it already exists.
Example from the Github page:
from django.db.models import CharField, Model
from autoslug import AutoSlugField
class Article(Model):
title = CharField(max_length=200)
slug = AutoSlugField(populate_from='title', unique=True)
From the docs:
# globally unique, silently fix on conflict ("foo" --> "foo-1".."foo-n")
slug = AutoSlugField(unique=True)

Django not assigning any primary key

When saving my table Django does not seem to be assigning any primary key? I'm I missing something?
class Campaign(models.Model):
campaignid = models.CharField(max_length=255, primary_key=True, db_column='campaignID')
name = models.CharField(max_length=105)
active = HibernateBooleanField(default=False)
created = models.DateTimeField()
modified = models.DateTimeField(null=True, blank=True)
companyid = models.ForeignKey(Company, null=True, db_column='companyID', blank=True)
class Meta:
db_table = u'campaign'
def __unicode__(self):
return self.name
Ok as my model was generated from an existing table using guid I had to keep it as CharField adding this seemed to work:
def save(self, *args, **kwargs):
if not self.campaignid:
self.campaignid = hashlib.sha1(str(random.random())).hexdigest()
super(Campaign, self).save(*args, **kwargs)

Categories

Resources