Hay, i have a simple model
class Manufacturer(models.Model):
name = models.CharField()
car_count = models.IntegerField()
class Car(models.Model):
maker = ForeignKey(Manufacturer)
I want to update the car_count field when a car is added to a manufacturer, I'm aware i could just count the Manufacturer.car_set() to get the value, but i want the value to be stored within that car_count field.
How would i do this?
EDIT
Would something like this work?
def save(self):
if self.id:
car_count = self.car_set.count()
self.save()
The best way make something happen when a model is saved it to use a signal. Django's documentation does a good job of describing what signals are and how to use them: http://docs.djangoproject.com/en/dev/topics/signals/
I'm not sure why you need to make it a field in the model though. Databases are very good at counting rows, so you could add a model method to count the cars which would use a very fast COUNT() query.
class Manufacturer(models.Model):
name = models.CharField()
def car_count(self):
return Car.objects.filter(maker=self).count()
class Car(models.Model):
maker = ForeignKey(Manufacturer)
In light of the requirement added by your comment, you're back to updating a field on the Manufacturer model whenever a Car is saved. I would still recommend using the count() method to ensure the car_count field is accurate. So your signal handler could look something like this:
def update_car_count(sender, **kwargs):
instance = kwargs['instance']
manufacturer = instance.maker
manufacturer.car_count = Car.objects.filter(maker=self).count()
manufacturer.save()
Then you would connect it to both the post_save and post_delete signals of the Car model.
post_save.connect(update_car_count, sender=Car)
post_delete.connect(update_car_count, sender=Car)
The proper way to let the database show how many cars a manufacturer has, is to let the database calculate it in the view using aggregations.
from django.db.models import Count
Manufacturer.objects.all().annotate(car_count=Count(car)).order_by('car_count')
Databases are very efficient at that sort of thing, and you can order by the result as seen above.
I'm a tiny bit confused.
.. when a car is added to a manufacturer ..
In the code shown in your question, I'd guess, you save a car with some manufacturer, e.g.
car.maker = Manufacturer.objects.get(name='BMW')
car.save()
Then the save method of the Car class would need to update the car_count of the manufacturer (see Overriding predefined model methods for more details).
def save(self, *args, **kwargs):
if self.id:
self.maker.car_count = len(self.maker.car_set.all())
super(Car, self).save(*args, **kwargs)
Since this isn't the most elegant code, I'd suggest as #Josh Wright to look into signals for that matter.
P.S. You could also add a method on the Manufacturer class, but I guess, you want this attribute to live in the database.
class Manufacturer(models.Model):
name = models.CharField()
def _car_count(self):
return len(self.car_set.all())
car_count = property(_car_count)
...
The override in MYYN's answer won't work, since Car.id won't be set (and probably not included in the Manufacturer's car_set) until it's saved. Instead, I'd do something like:
def save(self, *args, **kwargs):
super(Car, self).save(*args, **kwargs)
self.maker.car_count = len(self.maker.car_set.all())
self.maker.save()
Which is untested, but should work.
Of course, the best way is to use Josh's solution, since that's going 'with the grain' of Django.
Related
I have a custom save method for a model.
class Ticket(models.Model):
show = models.ForeignKey(Show)
seat = models.ForeignKey(Seat)
ref = models.CharField(max_length=100)
paid = models.BooleanField(default=False)
class Meta:
unique_together = ('show', 'seat')
def save(self, *args, **kwargs):
if self.paid:
do_something()
In the view I would like to update multiple Ticket objects:
Ticket.objects.filter(ref='ref').update(paid=True)
But, since this won't call the custom save method. The method do_something() won't be processed. Is there any way to solve this problem?
The obvious solution would be:
for ticket in Ticket.objects.filter(ref='ref'):
ticket.paid = True
ticket.save()
If you are doing the update for performance reasons you don't want to give up, you could do:
new_paid_tickets = Ticket.objects.filter(ref='ref')
new_paid_tickets.update(paid=True)
for ticket in new_paid_tickets:
do_something()
def save(self, *args, **kwargs):
if self.paid:
do_something()
super(Ticket, self).save(*args, **kwargs)
use this and add the custom code inside self.paid, hope it helps
Quote from docs:
Be aware that the update() method is converted directly to an SQL statement. It is a bulk operation for direct updates. It doesn’t run any save() methods on your models, or emit the pre_save or post_save signals (which are a consequence of calling save()), or honor the auto_now field option. If you want to save every item in a QuerySet and make sure that the save() method is called on each instance, you don’t need any special function to handle that. Just loop over them and call save().
So you need to iterate over queryset and call save() method for each element.
I want to list only usable items in OneToOneField not all items, its not like filtering values in ChoiceField because we need to find out only values which can be used which is based on the principle that whether it has been used already or not.
I am having a model definition as following:
class Foo(models.Model):
somefield = models.CharField(max_length=12)
class Bar(models.Model):
somefield = models.CharField(max_length=12)
foo = models.OneToOneField(Foo)
Now I am using a ModelForm to create forms based on Bar model as:
class BarForm(ModelForm):
class Meta:
model = Bar
Now the problem is in the form it shows list of all the Foo objects available in database in the ChoiceField using the select widget of HTML, since the field is OneToOneField django will force to single association of Bar object to Foo object, but since it shows all usable and unusable items in the list it becomes difficult to find out which values will be acceptable in the form and users are forced to use hit/trial method to find out the right option.
How can I change this behavior and list only those items in the field which can be used ?
Although this is an old topic I came across it looking for the same answer.
Specifically for the OP:
Adjust your BarForm so it looks like:
class BarForm(ModelForm):
class Meta:
model = Bar
def __init__(self, *args, **kwargs):
super(BarForm, self).__init__(*args, **kwargs)
#only provide Foos that are not already linked to a Bar, plus the Foo that was already chosen for this Bar
self.fields['foo'].queryset = Foo.objects.filter(Q(bar__isnull=True)|Q(bar=self.instance))
That should do the trick. You overwrite the init function so you can edit the foo field in the form, supplying it with a more specific queryset of available Foo's AND (rather important) the Foo that was already selected.
For my own case
My original question was: How to only display available Users on a OneToOne relation?
The Actor model in my models.py looks like this:
class Actor(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name = 'peactor')
# lots of other fields and some methods here
In my admin.py I have the following class:
class ActorAdmin(admin.ModelAdmin):
# some defines for list_display, actions etc here
form = ActorForm
I was not using a special form before (just relying on the basic ModelForm that Django supplies by default for a ModelAdmin) but I needed it for the following fix to the problem.
So, finally, in my forms.py I have:
class ActorForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ActorForm, self).__init__(*args, **kwargs)
#only provide users that are not already linked to an actor, plus the user that was already chosen for this Actor
self.fields['user'].queryset = User.objects.filter(Q(peactor__isnull=True)|Q(peactor=self.instance))
So here I make an ActorForm and overwrite the __init__ method.
self.fields['user'].queryset =
Sets the queryset to be used by the user formfield. This formfield is a ModelChoiceField
by default for a OneToOneField (or ForeignKey) on a model.
Q(peactor__isnull=True)|Q(peactor=self.instance)
The Q is for Q-objects that help with "complex" queries like an or statement.
So this query says: where peactor is not set OR where peactor is the same as was already selected for this actor
peactor being the related_name for the Actor.
This way you only get the users that are available but also the one that is unavailable because it is already linked to the object you're currently editing.
I hope this helps someone with the same question. :-)
You need something like this in the init() method of your form.
def __init__(self, *args, **kwargs):
super(BarForm, self).__init__(*args, **kwargs)
# returns Bar(s) who are not in Foo(s).
self.fields['foo'].queryset = Bar.objects.exclude(id__in=Foo.objects.all().values_list(
'bar_id', flat=True))
PS: Code not tested.
I've got a fairly complicated Django model that includes some fields that should only be saved under certain circumstances. As a simple example,
from django.db import models
class MyModel(models.Model):
name = models.CharField(max_length=200)
counter = models.IntegerField(default=0)
def increment_counter(self):
self.counter = models.F('counter') + 1
self.save(update_fields=['counter'])
Here I'm using F expressions to avoid race conditions while incrementing the counter. I'll generally never want to save the value of counter outside of the increment_counter function, as that would potentially undo an increment called from another thread or process.
So the question is, what's the best way to exclude certain fields by default in the model's save function? I've tried the following
def save(self, **kwargs):
if update_fields not in kwargs:
update_fields = set(self._meta.get_all_field_names())
update_fields.difference_update({
'counter',
})
kwargs['update_fields'] = tuple(update_fields)
super().save(**kwargs)
but that results in ValueError: The following fields do not exist in this model or are m2m fields: id. I could of course just add id and any m2m fields in the difference update, but that then starts to seem like an unmaintainable mess, especially once other models start to reference this one, which will add additional names in self._meta.get_all_field_names() that need to be excluded from update_fields.
For what it's worth, I mostly need this functionality for interacting with the django admin site; every other place in the code could relatively easily call model_obj.save() with the correct update_fields.
I ended up using the following:
from django.db import models
class MyModel(models.Model):
name = models.CharField(max_length=200)
counter = models.IntegerField(default=0)
default_save_fields = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.default_save_fields is None:
# This block should only get called for the first object loaded
default_save_fields = {
f.name for f in self._meta.get_fields()
if f.concrete and not f.many_to_many and not f.auto_created
}
default_save_fields.difference_update({
'counter',
})
self.__class__.default_save_fields = tuple(default_save_fields)
def increment_counter(self):
self.counter = models.F('counter') + 1
self.save(update_fields=['counter'])
def save(self, **kwargs):
if self.id is not None and 'update_fields' not in kwargs:
# If self.id is None (meaning the object has yet to be saved)
# then do a normal update with all fields.
# Otherwise, make sure `update_fields` is in kwargs.
kwargs['update_fields'] = self.default_save_fields
super().save(**kwargs)
This seems to work for my more complicated model which is referenced in other models as a ForeignKey, although there might be some edge cases that it doesn't cover.
I created a mixin class to make it easy to add to a model, inspired by clwainwright's answer. Though it uses a second mixin class to track which fields have been changed, inspired by this answer.
https://gitlab.com/snippets/1746711
On update, I'm trying to clear all associations from a model having ManyToMany association with another. However, the associations are still persisted.
To explain my point, consider the following 2 models:
class Teacher(models.Model):
school = models.ForeignKey(School)
students = models.ManyToManyField(Student)
class Student(models.Model):
school = models.ForeignKey(School)
Now, I wish to remove all associations of teacher to student, or vice-versa, whenever the shool changes for either model. So I decided to override the save() method for Teacher first.
class Teacher(models.Model):
...
...
def save(self, *args, **kwargs):
# after figuring out "school_id" changed
self.students.remove()
super(Teacher, self).save(*args, **kwargs)
However, once the Teacher object is persisted, I can see that all the Student associations are reinserted into the association table.
Am I missing out something here, or, is there some way to instruct the models.Model.save() not to reinsert the associations?
Thanks!
You need to use clear() method not remove(). According to docs, delete() removes a specified model objects from the related object set
>>> b = Blog.objects.get(id=1)
>>> e = Entry.objects.get(id=234)
>>> b.entry_set.remove(e) # Disassociates Entry e from Blog b.
But, that is not your case, you want to disassociate all of them not a specified one.
def save(self, *args, **kwargs):
# after figuring out "school_id" changed
self.students.clear()
super(Teacher, self).save(*args, **kwargs)
Is this correct?
class Customer(models.Model):
account = models.ForeignKey(Account)
class Order(models.Model):
account = models.ForeignKey(Account)
customer = models.ForeignKey(Customer, limit_choices_to={'account': 'self.account'})
I'm trying to make sure that an Order form will only display customer choices that belong to the same account as the Order.
If I'm overlooking some glaring bad-design fallacy, let me know.
The main thing I'm concerned with is:
limit_choices_to={'account': 'self.account'}
The only answer to 'is it correct' is 'does it work when you run it?' The answer to that of course is no, so I don't know why you're asking here.
There's no way to use limit_choices_to dynamically to limit based on the value of another field in the current model. The best way to do this is by customising the form. Define a ModelForm subclass, and override the __init__ method:
class MyOrderForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyOrderForm, self).__init__(*args, **kwargs)
if 'initial' in kwargs:
self.fields['customer'].queryset = Customer.objects.filter(account=initial.account)
You should set choices field of your order form (inherited from ModelForm) in the constructor.
limit_choices_to={'account': 'self.account'} is wrong, since foreign key to customer cannot point to Account.