I have created this model in Django which also saves date and slug. But even if I am making these changes again the str and the slug still remains the same and not updating as per the new data. How to fix this ?
class EntranceExamination(models.Model):
course = models.CharField(max_length=100, blank=True, default='')
year = models.PositiveIntegerField(choices=year_choices(),default=current_year(), validators=[MinValueValidator(1984), max_value_current_year])
month = models.PositiveIntegerField(choices=month_choices(),validators=[MinValueValidator(1), MaxValueValidator(12)])
day = models.PositiveIntegerField(validators=[MinValueValidator(1), MaxValueValidator(31)])
start_time = models.TimeField(blank=True)
end_time = models.TimeField(blank=True)
slug = models.SlugField(editable=False,max_length=100)
class Meta:
ordering = ['month']
def __str__(self):
return f"{self.course}'s exam on {self.slug}"
def save(self):
if not self.slug:
self.slug = f'{self.day}-{self.month}-{self.year}'
super(EntranceExamination, self).save()
__str__ is evaluated when you can str(…) on the object, so when data on the model changes, then __str__ will return something different.
As for slug, you can each time update the slug when you save the model:
class EntranceExamination(models.Model):
# …
def save(self, *args, **kwargs):
self.slug = f'{self.day}-{self.month}-{self.year}'
super().save(*args, **kwargs)
This will however not work when you update in bulk through the ORM, since ORM calls often circumvent the .save(…) method and triggers.
It is however not a good idea. Slugs are used for URI's, and as the W3 consortium says: "Cool URIs don't change" [w3.org]:
When you change a URI on your server, you can never completely tell
who will have links to the old URI. They might have made links from
regular web pages. They might have bookmarked your page. They might
have scrawled the URI in the margin of a letter to a friend.
When someone follows a link and it breaks, they generally lose
confidence in the owner of the server. They also are frustrated -
emotionally and practically from accomplishing their goal.
Enough people complain all the time about dangling links that I hope
the damage is obvious. I hope it also obvious that the reputation
damage is to the maintainer of the server whose document vanished.
Related
I have a model that needs to include the ID in another field when its created... see below:
id = models.AutoField(primary_key=True)
ticket_number = models.CharField(max_length=200, blank=True)
brand = models.ForeignKey(Brand, on_delete=models.SET_NULL, null=True)
...
def save(self, *args, **kwargs):
shortname = self.brand.shortname
super(Job, self).save(*args, **kwargs)
self.ticket_number = shortname.upper() + '-' + str(self.id)
super(Job, self).save(*args, **kwargs)
It works, which is good, and creates a unique ticket number. I'm new to Django, but I know enough that I feel like saving a post twice is inefficient. Is there something like .save(commit=False) for Models? I'd like to only save it once per save.
You can't really know in advance what self.id will be until the corresponding database row is created and Django extracts it from there. If you aren't planning on filtering on the column, you can just turn it into a property that's computed when you look it up on a model instance:
class Thing(db.Model):
id = models.AutoField(primary_key=True)
brand = models.ForeignKey(Brand, on_delete=models.SET_NULL, null=True)
#property
def ticket_number(self):
return '{}-{}'.format(self.brand.shortname.upper(), self.id)
You can use the post_save signal, to update the ticket number. The post_save signal gets called after your model is saved, hence it will have an id. Although, you will still be performing two saves, but you will only call save explicitly once.
Post save documentation: here
That will hopefully give you more mental peace =).
I added a new model to my app named SocialProfile, which is responsible for keeping social-related properties of a user which has a one-to-one relationship with UserProfile model. This is the SocialProfile model in models.py:
class SocialProfile(models.Model):
profile = models.OneToOneField('UserProfile', on_delete=models.CASCADE)
facebook_profiles = models.ManyToManyField('FacebookContact', related_name='synced_profiles', blank=True)
google_profiles = models.ManyToManyField('GoogleContact', related_name='synced_profiles', blank=True)
hash = models.CharField(max_length=30, unique=True, blank=True)
def save(self, *args, **kwargs):
if not self.pk:
hash = gen_hash(self.id, 30)
while SocialProfile.objects.filter(hash=hash).exists():
hash = gen_hash(self.id, 30)
self.hash = hash
def __str__(self):
return str(self.profile)
Right now, I keep a record for synced facebook & google profiles. Now, the problem is that creating new objects does not actually add any record in the database. I cannot create instances with scripts or admin. In case of scripts, the following runs without errors but no record is created:
for profile in UserProfile.objects.all():
sp = SocialProfile.objects.create(profile=profile)
print(profile, sp)
SocialProfile.objects.count()
The prints are done, and look correct and the count() returns 0. I try creating objects in admin, but I get the following error:
"{{socialprofile object}}" needs to have a value for field "socialprofile" before
this many-to-many relationship can be used.
I think that is another problem, because if I comment the Many-to-Many relationships, it is done, without error (still no new records). I mentioned it just if it might help.
I have checked the database, the tables are there, no new migrations are detected either.
Any help and idea about what could be the problem would be appreciated!
You've overwritten the save method so that it never actually saves anything. You need to call the superclass method at the end:
def save(self, *args, **kwargs):
if not self.pk:
...
return super(SocialProfile, self).save(*args, **kwargs)
I've been playing around with Django recently, however I'm stuck on how to approach this problem. I have a 'Person' model which has a one to many relationship with a 'Voucher' Model. Now the person has a quota and each time a Voucher is generated, the quota will decrease by one. What I'm stuck on is doing this through the save method. So how does one do this?
Below are my models:
class Person(AbstractDetail):
# Note: Model to maintain information about Person
vc = models.IntegerField(default = 3, verbose_name = 'Vouchers',
null = False, validators = [ validate_voucher ])
class Voucher(models.Model):
# Note: Model to maintain information about Voucher
vc = models.CharField (max_length = 25, verbose_name = 'Voucher ID',
help_text = 'Voucher Identifier')
ps = models.ForeignKey(Person, on_delete = models.CASCADE,
verbose_name = 'Person')
Don't do it in save(), it's easily messed up with django. Try to use django signal post_save():
from django.db.models.signals import post_save
#receiver(post_save, sender=Voucher)
def decrease_quota(sender, instance, created, *args, **kwargs):
if created:
instance.vc -= 1
instance.save()
Check django doc for django signals.
I'd take a slight performance hit in favour of avoiding the duplication of the voucher count information (call me old-fashioned).
In stead of saving the voucher count in Person.vc, I'd evaluate the voucher count when needed by creating a function for that in the Voucher object manager:
def get_voucher_count(self, person):
return self.filter(ps=person).count()
OK, I manage to work it out (with the help of Shang Wang & Ytsen de Boer answer as well, which I'm grateful for). The way which I did it was to create an instance of the object Person and take away from it there.
Fortunately enough, I quick time check the doc's which Shang just showed me (and look way deeper as well). Below is the way to do it:
#receiver(post_save, sender = Voucher, dispatch_uid = "voucher_identifier")
def decrease_quota(sender, instance, **kwargs):
obj = Person.objects.get(pk = instance.ps.id)
if obj.vc < 4 and obj.vc > 0:
obj.vc = obj.vc - 1
obj.save()
Of course I have to do abit more to it (simple validations and stuff like that), but this how I needed it.
I'm creating Django application, with forum-like thingy. One of the views should display list of Discussions, with last written post beside it.
class Discussion(models.Model):
<snip>
topic = models.CharField(max_length=512)
class DiscussionPost(models.Model):
<snip>
target = models.ForeignKey(Discussion)
author = models.ForeignKey(User)
content = models.TextField(max_length=16000)
creation_date = models.DateTimeField(auto_now_add=True)
With standard Django queries, I would have to fetch ~50 times per page (one for each discussion).
DiscussionPost.objects
.filter(target=some_discussion)
.annotate(last_post=Max('creation_date'))
.filter(creation_date=F('last_post'))
I tried to work this around by adding field last_post = models.ForeignKey(DiscussionPost, null=True) to discussion, and changing 'save' method in DiscussionPost like this:
def save(self, *args, **kwargs):
if self.pk == None:
i_am_new = True
else:
i_am_new = False
super(DiscussionPost, self).save(*args, **kwargs)
if i_am_new:
self.target.last_post=self
self.target.save()
But this makes circular dependency, and simply won't compile.
Does anyone know a way to solve this problem? It seems easy, but I'm stuck...
To solve your circular dependency:
The problem is: DiscussionPost has not been declared yet when you FK it in Discussion.
put the name of the model that has not been declared yet in quotes.
models.ForeignKey('DiscussionPost', null=True)
see: https://stackoverflow.com/a/9606701/884453
from django.db import models
from django.contrib.auth.models import User
class Product(models.Model):
name = models.CharField(max_length = 127)
description = models.TextField()
code = models.CharField(max_length = 30)
lot_no = models.CharField(max_length = 30)
inventory = models.IntegerField()
commited = models.IntegerField()
available = models.IntegerField()
reorder = models.IntegerField()
created_date = models.DateField(auto_now_add = True)
comment_user = models.ForeignKey(User, null=True)
comment_txt = models.TextField()
def __unicode__(self):
return self.code + " - " + self.name + " - " + self.lot_no + " - " + str(self.created_date)
#property
def available(self):
return self.inventory - self.commited
Im trying to have available calculated by (inventory - self) when a person enters in the data for inventory and commited in django admin template. But I'm not sure how.
Thanks,
Jon
Try overriding the save method on the model:
def save(self, *args, **kwargs):
"update number available on save"
self.available = self.inventory - self.committed
super(Product, self).save(*args, **kwargs)
You could also put logic in there that would do something if self.available became negative.
It seems like you may have two problems; the overlapping available property+field and availability not showing up as you expect in the admin.
Choose one way (property or field) to represent the availability and go with it. Don and Seth have shown a way to do it using a field and Daniel and Ignacio have suggested going with a property.
Since you really want this field to show up in the admin just go with the field; give it a helpful help_text="...", remove the #property, and override save().
class Product(models.Model):
# ...
availability = models.IntegerField(help_text="(updated on save)")
# Use Seth's save()
def save(self, *args, **kwargs):
self.availability = self.inventory - self.commited
super(Product, self).save(*args, **kwargs)
This is not the best way to do things in terms of normalized data but it will probably be the simplest solution to your current problem.
If you are using trunk instead of Django-1.1.1, you can also use readonly_fields in the admin.
Don is correct that you have the name available duplicated, because you have both a field and a property. Drop the field.
This is what I said when I gave you the solution to this problem in your original question - I explicitly said "drop the existing 'available' field". Following half a solution is never going to work.
However I fundamentally disagree with Seth and Don who recommend overriding the save() function to calculate this value. That is a totally unnecessary duplication of data. The property is the correct solution.
You've bound both a Django field and a vanilla Python property to the same name on your model. One of these attributes is masking the other, which is why you're getting unexpected behavior in the Django admin. This is almost certainly not what you want.
Override the save method and remove your def available property entirely.
Ignacio is trying to help you keep your data normalized by not storing information in your DB twice. It's a good practice to follow in the general case, but there are many times when you want to store calculated values in your DB. This seems like a practical use of data duplication.
The property is actually removing the available integer field off the admin page it seems
See the Django documentation for model properties