When i save the model from admin interface a first time i need to autogenerate the slug. I use slugify and pre_save signal and my slug field have unique=True option. But when i push save button the object slugfield raise validation error (Required field) because field is unique, i think. I thought that pre_save goes before validation. Or i am wrong?
# models.py
class Slugified(models.Model):
slug = models.CharField(unique=True, max_length=50)
class Meta:
abstract = True
class Post(Slugified):
title = models.Charfield(max_length=50)
#receiver(pre_save, sender=Post)
def save_slug(sender, instance, *args, **kwargs):
instance.slug = slugify(instance.title)
The form automatically generated by the admin sees the slug field as a required field. The pre_save signal receiver works correctly, but the code never tries to save the model as the form doesn't validate.
The solution to this is to all blank values:
class Slugified(models.Model):
slug = models.CharField(unique=True, max_length=50, blank=True)
This way, the field is not required in the form and the slug gets set before the instance is saved.
Also, please note that your code does not correctly handle duplicate slugs. If two post titles generate identical slugs, an IntegrityError would be raised. This is easier solved in the save method than in the pre_save signal receiver:
class Slugified(models.Model):
slug = models.CharField(unique=True, max_length=50, blank=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
while True:
try:
return super(Slugified, self).save(*args, **kwargs)
except IntegrityError:
# generate a new slug
Related
I want to update m2m field on save() method
I have the following model:
class Tag(models.Model):
label = models.CharField(max_length=50)
parents_direct = models.ManyToManyField("Tag", related_name="children", blank=True)
created = models.DateTimeField(auto_now_add=True)
description = models.TextField(null=True, blank=True)
administrators = models.ManyToManyField(
to=KittyUser, related_name="administrated_tags", blank=True)
moderators = models.ManyToManyField(
to=KittyUser, related_name="moderated_tags", blank=True)
allowed_users = models.ManyToManyField(
to=KittyUser, related_name="allowed_tags", blank=True)
visible = models.BooleanField(default=True, verbose_name="visible to anyone")
POSTABILITY_CHOICES = (
('0', 'any allowed user can post'),
('1', 'only moderators\\admins can post')
)
postability_type = models.CharField(default='0',
max_length=1,
choices=POSTABILITY_CHOICES)
parents_tree = models.ManyToManyField("Tag", related_name="parents_tree_in_for", blank=True)
related_tags = models.ManyToManyField("Tag", related_name="related_tags_in_for", blank=True)
def save(self, *args, **kwargs):
self.label="overriden label"
super(Tag, self).save(*args, **kwargs)
self.parents_tree.add(*self.parents_direct.all())
breakpoint()
super(Tag, self).save(*args, **kwargs)
Through django-admin the tags get created, the label substitution works, though parents_tree don't get updated.
If I create it from the shell, it is swearing at the second super().save:
django.db.utils.IntegrityError: duplicate key value violates unique constraint "posts_tag_pkey"
If you take away the first super.save(), you get the following:
"<Tag: overriden label>" needs to have a value for field "id" before this many-to-many relationship can be used.
in both shell and admin.
The question is, how to update my m2m field on save?
Another question is why does it work differently in admin panel and shell?
As a temporary solution I managed to listen to the signal of updating parents_direct field, but what if I wanted to depend on non-m2m fields?
from django.db.models.signals import m2m_changed
def tag_set_parents_tree(sender, **kwargs):
if kwargs['action'] == 'post_add' or 'post_remove':
parents_direct = kwargs['instance'].parents_direct.all()
if parents_direct:
kwargs['instance'].parents_tree.set(parents_direct)
for tag in parents_direct:
kwargs['instance'].parents_tree.add(*tag.parents_tree.all())
else:
kwargs['instance'].parents_tree.clear()
m2m_changed.connect(tag_set_parents_tree, sender=Tag.parents_direct.through)
I want to keep the latest record of a user:
class Record(models.Model):
name = models.ForeignKey(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='site_pics')
date = models.DateTimeField(null=True, blank=True, auto_now_add=True)
def save(self, *args, **kwargs):
super(Record, self).save(*args, **kwargs)
numdata = Record.objects.select_related('name').count()
print(numdata)
#if numdata > 1:
# Record.objects.select_related('name').first().delete()
As per this post Filter latest record in Django, I tried:
.distinct()
.select_related()
.prefetch_related()
None of them return the correct number of records or don't work at all because I'm using SQLite.
Thank you for any suggestions
In that case it might be better to change the modeling to a OneToOneField, such that the database will reject a second Record for the same user. You might also want to change the name of the relation to user, since you are referring to a user object, not its name.
In the save(…) method, you can look if the primary key is not yet filled in, if that is the case, you can delete the original record of the user. Regardless whether that exists or not. If this records does not exist, then it will act as no-op, where nothing changes in the database:
class Record(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='site_pics')
date = models.DateTimeField(null=True, blank=True, auto_now_add=True)
def save(self, *args, force_insert=False, **kwargs):
if self.pk is None or force_insert:
Record.objects.filter(user_id=self.user_id).delete()
return super().save(*args, force_insert=force_insert, **kwargs)
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
I have two models (Post and Display). Both have Datetime-auto fields. My problem is that i want to update all display objects related to a post, once a post is updated.
I have read here that you could override one models save method, but all the examples are About updating the model with the foreign key in it and then call the save method of the other model. In my case it's the other way arround. How can i do this ?
class Post(models.Model):
title = models.CharField(max_length=40)
content = models.TextField(max_length=300)
date_posted = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
rooms = models.ManyToManyField(Room, related_name='roomposts', through='Display')
def __str__(self):
return self.title
def get_absolute_url(self):
return "/post/{}/".format(self.pk)
class Display(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
room = models.ForeignKey(Room, on_delete=models.CASCADE)
isdisplayed = models.BooleanField(default=0)
date_posted = models.DateTimeField(auto_now=True)
def __str__(self):
return str(self.isdisplayed)
i want to update the date_posted of all related Display-objects once their related post is changed. I do not know if overriding the save-method works here.
in this case you should have a look at django's reverse foreign key documentation
https://docs.djangoproject.com/en/2.2/topics/db/queries/#following-relationships-backward
in your case you can override the save method on your Post model
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
#either: this excutes many sql statments
for display in self.display_set.all():
display.save()
#or faster: this excute only one sql statements,
#but note that this does not call Display.save
self.display_set.all().update(date_posted=self.date_posted)
The name display_set can be changed using the related_name option
in Display, you can change it:
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='displays')
Then, instead of using self.display_set in your code, you can use self.displays
Overriding save method works, but that's not were you should go, imo.
What you need is signals:
#receiver(post_save, sender=Post)
def update_displays_on_post_save(sender, instance, **kwargs):
if kwargs.get('created') is False: # This means you have updated the post
# do smth with instance.display_set
Usually it goes into signals.py.
Also you need to include this in you AppConfig
def ready(self):
from . import signals # noqa
I have models like this:
class Devices(models.Model):
name = models.CharField(max_length=255, blank=True)
uniqueid = models.CharField(db_column='uniqueid'.lower(), max_length=255, blank=True) # Field name made lowercase.
latestposition = models.ForeignKey('Positions', db_column='latestPosition_id'.lower(), blank=True, null=True) # Field name made lowercase.
class Meta:
db_table = 'devices'
verbose_name = 'Devices'
verbose_name_plural = verbose_name
def __unicode__(self):
return '%s' %(self.name)
# Call the signal to create user device when the device is created.
dispatcher.connect(save_user_device, signal=post_save, sender=Devices)
class UsersDevices(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
devices = models.ForeignKey('Devices')
class Meta:
db_table = 'users_devices'
verbose_name = 'User Devices'
verbose_name_plural = verbose_name
def __unicode__(self):
return '%s %s' %(self.user, self.devices)
When the Devices is created, I want to create users devices. user field in the UsersDevices would be signed in user who created device and devices would be the device that was just created.
def save_user_device(sender, instance, **kwargs):
## Problem is here
instance.UsersDevices.create( )
How can I create a UsersDevices using this signal with the user instance and device instance
You don't really need signals in this case. Overwrite the save() method of the model:
def save(self, *args, **kwargs):
# Call the original save function to actually save the model:
super(Devices, self).save(*args, **kwargs)
# Now this model is saved, so we can create the UsersDevices
UserDevices(user=get_user_from_somewhere(), devices=self).save()
See the documentation for more information about overwriting the save() method:
https://docs.djangoproject.com/en/1.7/topics/db/models/#overriding-model-methods
It is better to redefine save method of Devices model for this purpose. But if you want to use signals it might be done like this:
def save_user_device(sender, instance, **kwargs):
## Problem is here
UsersDevices.create(devices=instance, user=instance.user)
In this case you have to add 'user' field to Devices model.
And small tip: you should give names to models singularly, plural names is bad style.
In my model I overwrite the save-method for my blog model to auto-populate the slug field using the slugify method.
class BlogPost(models.Model):
title = models.CharField(max_length=100,unique=True)
slug = models.SlugField(max_length=100,unique=True)
date = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(Author)
body = models.TextField()
category = models.ForeignKey(BlogCategory)
def save(self, *args, **kwargs):
if not self.id:
# Newly created object, so set slug
self.slug = slugify(self.title)
super(BlogPost, self).save(*args, **kwargs)
But creating a new object in the admin interface doesn't work without either setting the slug field manually or doing something like
class BlogPostAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("title",)}
Basically I currently have the same functionality defined twice. Any ideas on how to avoid this? And: why doesn't work my own save method in the admin?
You should put blank=True in the definition of the slug field.