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
Related
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.
I am trying to find guidance on how to build a model representing the following:
We have IT environments (consisting of multiple components, like webservers, databases, etc.)
SLAs are not specific to one environment, it's more a set of general contracts a concrete environment references to (aka should be a separate table)
each environment must have at least one or more SLA(s) associated
From all SLAs associated to an environment exactly one must have the state "effective"
I implemented a model sufficiently reflecting the first two points (at least, I think so), but especially the last point seems to be cumbersome.
Sufficiently meaning, using this implementation, the relation using the cross table is optional, not mandatory. This is OK currently, but not in the long run.
class Environment(models.Model):
fullname = models.CharField(max_length=45)
...
sla = models.ManyToManyField(SLA, through='EnvironmentSLA')
creation_date = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = (('fullname', 'projectid', 'regionid', 'account'),)
class SLA(models.Model):
description = models.CharField(max_length=255)
reaction_time = models.CharField(max_length=45)
service_level = models.CharField(max_length=45)
creation_date = models.DateTimeField(auto_now_add=True)
class EnvironmentSLA(models.Model):
PLANNED = 'pl'
EFFECTIVE = 'ef'
DEPRECATED = 'dp'
SLA_STATE = (
( PLANNED, 'planned' ),
( EFFECTIVE, 'effective'),
( DEPRECATED, 'deprecated'),
)
environment = models.ForeignKey('Environment', on_delete=models.CASCADE)
sla = models.ForeignKey(SLA, on_delete=models.CASCADE)
state = models.CharField(max_length=2, choices=SLA_STATE, default=PLANNED)
So my questions are:
Am I generally on the right track, but capturing the last constraint is not possible solely focusing on the model?
What would be an elegant way?
We implemented a save() method for the model EnvironmentSLA that does the following:
check if the object to be saved set state == EFFECTIVE
if not just save the object
try to get currently effective SLA
change the state SLA of the currently effective SLA to DEPRECATED
save the object
The save()-function looks like this:
class EnvironmentSLA(models.Model):
[...]
def save(self, *args, **kwargs):
if self.state != self.EFFECTIVE:
super(EnvironmentSLA, self).save(*args, **kwargs)
return
try:
effective_sla = EnvironmentSLA.objects.filter(environment=self.environment, state=self.EFFECTIVE).get()
except Exception as e:
effective_sla = None
if effective_sla:
effective_sla.state = self.DEPRECATED
effective_sla.save()
super(EnvironmentSLA, self).save(*args, **kwargs)
This way we don't loose the defined SLAs but always have only one active SLA.
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.
Let's say I have a model Issue that looks like this:
class Issue(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=100)
text = models.TextField()
And another one called Comment like so:
class Comment(models.Model):
issue = models.ForeignKey(Issue)
text = models.TextField()
number = models.AutoField()
I want the comment number field to be relative to the issue.
Which means that every issue will have its separate comment#1, comment#2, comment#3...etc.
Can this be done in Django?
If you removed the number field from your Comment model and replaced it with a date_added field, you could still query the DB to pull all comments associated with a certain entry in order.
class Comment(models.Model):
issue = models.ForeignKey(Issue)
text = models.TextField()
date_added = models.DateField(auto_now_add = True)
Then if you wanted to get all of the comments associated with a certain issue:
i = Issue.object.get(pk=1) #pk = primary key id for whatever issue you want
comments_by_issue = i.comment_set.all().order_by('date_added')
Now you have some comments that you can refer to by index location (comments_by_issue[0] would get you the first comment attached to the issue, for instance).
That index location is the closest way I can figure to get what you're looking for. Someone else mentioned comment.id, but this is just going to be an autoincrementing integer that goes up for every comment. The fifth comment added to your system might have comment.id = 5, but it might be the first comment attached to issue 2 - if I'm reading your query right, having the comment ID doesn't help in that context.
This is more of a workaround than a direct answer, but I hope it helps.
I prefer soundeux's solution. That said, here is the idea...
class Comment(models.Model):
# ...
number = models.PositiveIntegerField(default=1)
def save(self, *args, **kwargs):
obj = super(Comment, self).save(*args, **kwargs)
try:
last_comment = self.issue.comment_set.all().orderby('-number')[0]
except KeyError: # I think it's KeyError... for when there is no comments
pass
else:
Comment.objects.filter(id=self.id).update(number=last_comment.number+1)
return obj
Be careful - this will not be executed when Comments are being updated using the ORM's update method. Indeed, that's what I've used to avoid recursion (which is not a very good solution either). You might wanna implement it as a pre_save event instead of the save() (still won't respond to the update(), but will be more robust about potential recursion problems).
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