Django field choices not properly updating - django

I have two models, one that loads the other model it's titles in a choice field dynamically. I fixed it so far that if I add a new object to the model which the titles are used from by updating the choice list in the init method, the choice list gets updated immediately. However when I decide to choose it as option and save it I get: Select a valid choice. example is not one of the available choices. When I restart the server it does work, what I did:
model:
class Assessment(models.Model):
title = models.CharField(max_length=200)
SPECIFIC_REQUIREMENTS_CHOICES = ()
SPECIFIC_REQUIREMENTS_CHOICES_LIST = []
for sRequirement in SpecificRequirements.objects.all():
SPECIFIC_REQUIREMENTS_CHOICES_LIST.append((sRequirement.title, sRequirement.title))
SPECIFIC_REQUIREMENTS_CHOICES = SPECIFIC_REQUIREMENTS_CHOICES_LIST
sRequirementChoice = models.CharField(max_length=200, choices=SPECIFIC_REQUIREMENTS_CHOICES,
default='')
forms:
class AssessmentForm(forms.ModelForm):
class Meta:
model = Assessment
fields = ['title', 'sRequirementChoice']
def __init__(self, *args, **kwargs):
super(AssessmentForm, self).__init__(*args, **kwargs)
SPECIFIC_REQUIREMENTS_CHOICES_LIST = []
for sRequirement in SpecificRequirements.objects.all():
SPECIFIC_REQUIREMENTS_CHOICES_LIST.append((sRequirement.title, sRequirement.title))
SPECIFIC_REQUIREMENTS_CHOICES = SPECIFIC_REQUIREMENTS_CHOICES_LIST
self.fields['sRequirementChoice'].choices = SPECIFIC_REQUIREMENTS_CHOICES

That's not how Model choices work. You are not supposed to populate choices dynamically in models.
You should consider using a ForeignKey relation with SpecificRequirements in your model.

Related

Changing default dashes of select field in Django forms, empty_label attribute is working for ForeignKey field but not for CharField

I'm trying to set up a form that allows a tattoo shop to book consultations. There are 2 select fields: select an artist and select a tattoo style. Instead of the default first option of the select field being '--------', I would like them to say 'Preferred Artist' and 'Tattoo Style', respectively.
models.py:
class ConsultationRequest(models.Model):
tattoo_styles = [
('water-color', 'Water Color'),
('blackwork', 'Blackwork'),
]
preferred_artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
tattoo_style = models.CharField(max_length=50, choices = tattoo_styles )
forms.py:
class ArtInformationForm(ModelForm):
class Meta:
model = ConsultationRequest
fields = ['preferred_artist', 'tattoo_style']
def __init__(self, *args, **kwargs):
super(ArtInformationForm, self).__init__(*args, **kwargs)
self.fields['preferred_artist'].empty_label = 'Preferred Artist' # this works
self.fields['tattoo_style'].empty_label = 'Tattoo Style' # this doesn't work
When I display this form in a template, the first option for selecting an artist is 'Preferred Artist', but the first option for selecting a tattoo style is still the default dashes. Not sure why this is. I am thinking it has something to do with them being two different kinds of fields, one being a ForeignKey and one being a CharField. Thanks for any help with this.

How to create a choice field from many to many connection

In my Django model I have a many to many connection. I would also like to have the option of selecting a primary diagnosis from the connected diagnoses.
class Case(models.Model):
diagnoses_all_icd_10 = models.ManyToManyField('ICD10')
How can I create a choice field that displays only the associated diagnoses for selection? It is important that the solution also works in the Django admin.
I think through argument works for you.
https://docs.djangoproject.com/en/2.0/ref/models/fields/#django.db.models.ManyToManyField.through
In your case:
class Case(models.Model):
diagnoses_all_icd_10 = models.ManyToManyField(ICD10, through='DiagnoseOrder')
class DiagnoseOrder(models.Model):
case = models.ForeignKey(Case, on_delete=models.CASCADE)
icd_10 = models.ForeignKey(ICD10, on_delete=models.CASCADE)
is_primary = models.BooleanField(default=False)
def save(self, *args, **kwargs):
# If not self.is_primary you won't need further query
if self.is_primary:
# Query if there is a primary order related to this case
existing_primary = DiagnoseOrder.objects.filter(is_primary=True, case=self.case).first()
if existing_primary:
# You can change existing primary's status *up to your need
existing_primary.is_primary = False
existing_primary.save()
super(DiagnoseOrder, self).save(*args, **kwargs)
Then, you can use InlineModelAdmin for Django admin customization.
Further reading:
https://docs.djangoproject.com/en/2.0/ref/contrib/admin/#django.contrib.admin.StackedInline
https://docs.djangoproject.com/en/2.0/ref/contrib/admin/#django.contrib.admin.TabularInline

Using an instance's fields to filter the choices of a manytomany selection in a Django admin view

I have a Django model with a ManyToManyField.
1) When adding a new instance of this model via admin view, I would like to not see the M2M field at all.
2) When editing an existing instance I would like to be able to select multiple options for the M2M field, but display only a subset of the M2M options, depending on another field in the model. Because of the dependence on another field's actual value, I can't just use formfield_for_manytomany
I can do both of the things using a custom ModelForm, but I can't reliably tell whether that form is being used to edit an existing model instance, or if it's being used to create a new instance. Even MyModel.objects.filter(pk=self.instance.pk).exists() in the custom ModelForm doesn't cut it. How can I accomplish this, or just tell whether the form is being displayed in an "add" or an "edit" context?
EDIT: my relevant code is as follows:
models.py
class LimitedClassForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(LimitedClassForm, self).__init__(*args, **kwargs)
if not self.instance._adding:
# Edit form
clas = self.instance
sheets_in_course = Sheet.objects.filter(course__pk=clas.course.pk)
self.Meta.exclude = ['course']
widget = self.fields['active_sheets'].widget
sheet_choices = []
for sheet in sheets_in_course:
sheet_choices.append((sheet.id, sheet.name))
widget.choices = sheet_choices
else:
# Add form
self.Meta.exclude = ['active_sheets']
class Meta:
exclude = []
admin.py
class ClassAdmin(admin.ModelAdmin):
formfield_overrides = {models.ManyToManyField: {
'widget': CheckboxSelectMultiple}, }
form = LimitedClassForm
admin.site.register(Class, ClassAdmin)
models.py
class Course(models.Model):
name = models.CharField(max_length=255)
class Sheet(models.Model):
name = models.CharField(max_length=255)
course = models.ForeignKey(Course)
file = models.FileField(upload_to=getSheetLocation)
class Class(models.model):
name = models.CharField(max_length=255)
course = models.ForeignKey(Course)
active_sheets = models.ManyToManyField(Sheet)
You can see that both Sheets and Classes have course fields. You shouldn't be able to put a sheet into active_sheets if the sheet's course doesn't match the class's course.

filtering ForeignKey by object ID

I have a CarType that has a ForeignKey BodyMaterial in my models.py:
class BodyMaterial(models.Model):
location = models.ForeignKey('CarType')
name = models.CharField(max_length=255)
class CarType(models.Model):
name = models.CharField(max_length=255)
default_body_material = models.ForeignKey(BodyMaterial, null = True, blank = True, default = "", limit_choices_to={'location__exact': 1})
BodyMaterial is an Inline in CarType in my admin.py:
class BodyMaterial_Inline(admin.StackedInline):
model = BodyMaterial
extra = 1
class CarType_Admin(admin.ModelAdmin):
inlines = [BodyMaterial_Inline]
admin.site.register(CarType, CarType_Admin)
I would like to filter the ForeignKey for default_body_material to show only the relevant BodyMaterials (the ones that appear/added on the same admin page). For example, I created a 2 seat CarType and in the same page added some BodyMaterials. Then I create an SVU CarType and some other BodyMaterials. When I go back to the 2 seat CarType, I would like to see only the relevant BodyMaterials in the drop-down for default_body_material.
I try to filter using limit_choices_to on the id. So I'm doing this using post_init because the id for the object in determined in runtime:
def setID(**kwargs):
instance = kwargs.get('instance')
default_body_material = instance._meta.get_field_by_name('default_body_material')[0]
default_body_material.limit_choices_to = {'location__exact': instance.id}
post_init.connect(setID, CarType)
Unfortunately, that does nothing. What am I missing? Is there a beter why of filtering ForeignKey for my purposes (this is probably very basic)?
Note that this question is only for the admin interface.
Just use a custom ModelForm:
class CarTypeAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(CarTypeAdminForm, self).__init__(*args, **kwargs)
# Can't limit it until instance has been saved at least once
if self.instance.pk:
self.fields['default_body_material'].queryset = \
self.fields['default_body_material'].queryset \
.filter(location=self.instance)
class CarTypeAdmin(admin.ModelAdmin):
form = CarTypeAdminForm
...
You want to look at overriding the queryset function for your inline.

field choices() as queryset?

I need to make a form, which have 1 select and 1 text input. Select must be taken from database.
model looks like this:
class Province(models.Model):
name = models.CharField(max_length=30)
slug = models.SlugField(max_length=30)
def __unicode__(self):
return self.name
It's rows to this are added only by admin, but all users can see it in forms.
I want to make a ModelForm from that. I made something like this:
class ProvinceForm(ModelForm):
class Meta:
CHOICES = Province.objects.all()
model = Province
fields = ('name',)
widgets = {
'name': Select(choices=CHOICES),
}
but it doesn't work. The select tag is not displayed in html. What did I wrong?
UPDATE:
This solution works as I wanto it to work:
class ProvinceForm(ModelForm):
def __init__(self, *args, **kwargs):
super(ProvinceForm, self).__init__(*args, **kwargs)
user_provinces = UserProvince.objects.select_related().filter(user__exact=self.instance.id).values_list('province')
self.fields['name'].queryset = Province.objects.exclude(id__in=user_provinces).only('id', 'name')
name = forms.ModelChoiceField(queryset=None, empty_label=None)
class Meta:
model = Province
fields = ('name',)
Read Maersu's answer for the method that just "works".
If you want to customize, know that choices takes a list of tuples, ie (('val','display_val'), (...), ...)
Choices doc:
An iterable (e.g., a list or tuple) of
2-tuples to use as choices for this
field.
from django.forms.widgets import Select
class ProvinceForm(ModelForm):
class Meta:
CHOICES = Province.objects.all()
model = Province
fields = ('name',)
widgets = {
'name': Select(choices=( (x.id, x.name) for x in CHOICES )),
}
ModelForm covers all your needs (Also check the Conversion List)
Model:
class UserProvince(models.Model):
user = models.ForeignKey(User)
province = models.ForeignKey(Province)
Form:
class ProvinceForm(ModelForm):
class Meta:
model = UserProvince
fields = ('province',)
View:
if request.POST:
form = ProvinceForm(request.POST)
if form.is_valid():
obj = form.save(commit=True)
obj.user = request.user
obj.save()
else:
form = ProvinceForm()
If you need to use a query for your choices then you'll need to overwrite the __init__ method of your form.
Your first guess would probably be to save it as a variable before your list of fields but you shouldn't do that since you want your queries to be updated every time the form is accessed. You see, once you run the server the choices are generated and won't change until your next server restart. This means your query will be executed only once and forever hold your peace.
# Don't do this
class MyForm(forms.Form):
# Making the query
MYQUERY = User.objects.values_list('id', 'last_name')
myfield = forms.ChoiceField(choices=(*MYQUERY,))
class Meta:
fields = ('myfield',)
The solution here is to make use of the __init__ method which is called on every form load. This way the result of your query will always be updated.
# Do this instead
class MyForm(forms.Form):
class Meta:
fields = ('myfield',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Make the query here
MYQUERY = User.objects.values_list('id', 'last_name')
self.fields['myfield'] = forms.ChoiceField(choices=(*MYQUERY,))
Querying your database can be heavy if you have a lot of users so in the future I suggest some caching might be useful.
the two solutions given by maersu and Yuji 'Tomita' Tomita perfectly works, but there are cases when one cannot use ModelForm (django3 link), ie the form needs sources from several models / is a subclass of a ModelForm class and one want to add an extra field with choices from another model, etc.
ChoiceField is to my point of view a more generic way to answer the need.
The example below provides two choice fields from two models and a blank choice for each :
class MixedForm(forms.Form):
speaker = forms.ChoiceField(choices=([['','-'*10]]+[[x.id, x.__str__()] for x in Speakers.objects.all()]))
event = forms.ChoiceField(choices=( [['','-'*10]]+[[x.id, x.__str__()] for x in Events.objects.all()]))
If one does not need a blank field, or one does not need to use a function for the choice label but the model fields or a property it can be a bit more elegant, as eugene suggested :
class MixedForm(forms.Form):
speaker = forms.ChoiceField(choices=((x.id, x.__str__()) for x in Speakers.objects.all()))
event = forms.ChoiceField(choices=(Events.objects.values_list('id', 'name')))
using values_list() and a blank field :
event = forms.ChoiceField(choices=([['','-------------']] + list(Events.objects.values_list('id', 'name'))))
as a subclass of a ModelForm, using the one of the robos85 question :
class MixedForm(ProvinceForm):
speaker = ...