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.
Related
from model.blah import Ghosts
I have a model has a with a filed looks like this
scary_boos = ArrayField(
choice_char_field(Ghosts.TYPE_SELECTION), blank=True, null=True
)
and in the admin panel, I am trying to add a form to show that field with pre-determined choices.
class GhostBoosForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
print(self.fields["mortgage_type"])
self.fields["scary_boos"].widget = CheckboxSelectMultiple(
choices=self.fields["scary_boos"].choices
)
class Meta:
model = GhostBoos
fields = "__all__"
however choices=self.fields["scary_boos"].choices doesn't work is there any other way to access those choices of the filed?
To do it that way, you'll need to access the base_field attribute of self.fields["scary_boos"]. ArrayField (which looks to be something that only works with PostgreSQL) basically stores it's first argument there, which I think in your case is a normal CharField(?)
More info: https://docs.djangoproject.com/en/2.2/ref/contrib/postgres/fields/#arrayfield
(I had to play around with self.fields a bit to find base_field, but it seems to give you access to what you need).
So: something like self.fields["scary_boos"].base_field.choices[1:] should do what you need. We "slice" off the first result because Django appears to insert the blank option ("", "-----") (at least for me, Django 2.2.7).
Hope that helps.
I have a model field with choices:
class MyModel(models.Model):
myfield = models.CharField(max_length=1000, choices=(('a','a'),('b','b'))
I know that I can access in forms this specific field and override its choices option like that:
self.instance._meta.get_field(field_name).choices = (('c','c'),('d','d'))
but that will change the choices for the entire model, not for an individual instance. What is the correct way to do it for one specific instance only or it is not possible?
I'm not aware of any way to change the model's field choices on a per-instance basis, but if it's for a form you can override the form's field choices (example written from memory so it might no be 100% accurate):
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, *args, **kw):
super(MyModelForm, self).__init__(*args, **kw)
if some_condition:
self.fields["myfield"].choices = (...)
Important: you want to override self.fields["myfield"], not self.myfield - the latter is a class attribute so changing it would affect all MyModelForm instances for the current process, something you won't usually notice when running the dev server but that will cause very erratic behaviour on production.
I'm having trouble overriding the save method on a Django model to check a restriction on a many-to-many field.
Say I have the following models:
class Person(models.Model):
name = models.CharField()
class ClothingItem(models.Model):
description = models.CharField()
owner = models.ForeignKey(Person)
class Outfit(models.Model):
name = models.CharField()
owner = models.ForeignKey(Person)
clothing_items = models.ManyToManyField(ClothingItem)
I would like to put a restriction on the save method of Outfit that ensures that each ClothingItem in a given outfit has the same owner as the Outfit itself.
I.e. I'd like to write:
class Outfit(models.Model):
name = models.CharField()
owner = models.ForeignKey(Person)
clothing_items = models.ManyToManyField(ClothingItem)
def save(self, *args, **kwargs):
for ci in self.clothing_items:
if ci.owner != self.owner:
raise ValueError('You can only put your own items in an outfit!)
super(Outfit, self).save(*args, **kwargs)
but when I try that I get an error about <Outfit: SundayBest>" needs to have a value for field "outfit" before this many-to-many relationship can be used.
Any ideas what's going wrong here?
There are two issues going on here. To directly answer your question, the error basically means: You cannot refer to any m2m relationship if the original object(an instance of Outfit here) is not saved in database.
Sounds like you are trying to do the validation in save() method, which is a pretty bad practice in django. The verification process should typically happen in Form that creates Outfit objects. To override default django form, please refer to django ModelAdmin.form. To understand how to do validation on django forms, check ModelForm validation.
If you want code to refer to for m2m validation, I found a good example from SO.
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.
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.