django set ModelChoiceField's "to_field_name" to multiple columns - django

I want to provide multiple field names in Django's modelChoiceField's to_field_name
something like
field1 = forms.ModelChoiceField(queryset=myModel.objects.all(), required=False,
empty_label="--------",
to_field_name=["col1, col2"],
widget=forms.Select(attrs={'class':'form-control'}),
)
I have a model like
class Codes(models.Model):
item_code = models.CharField(max_length=50)
item_name = models.CharField(max_length=50)
def __str__(self):
return self.item_code + self.item_name
Now I can set queryset as
field1 = forms.ModelChoiceField(queryset=Codes.objects.all(), required=False,
empty_label="--------",
widget=forms.Select(attrs={'class':'form-control'}),
)
And I will get the combination of two fields in my select tag options
everything works but when I open the edit page, the select box doesn't show the default selected value
Also, I want a combination of fields because when item_code repeats in the database, to_field_name gives an error.

I believe you are interpreting to_field_name wrong:
According to the Django website (Django to_field_name explained):
This optional argument is used to specify the field to use as the value of the choices in the field’s widget. Be sure it’s a unique field for the model, otherwise the selected value could match more than one object. By default it is set to None, in which case the primary key of each object will be used...
Which means you can use the to_field_name to address the form object in HTML.
But it does not say anything about multiple to_field_name values.
Normally the primary key is used to address the object. If you set the to_field_name then that field will be used.
To actually add data into the modelChoiceField object, you would need to do something like this.
class CustomForm(ModelForm):
muscles = forms.ModelMultipleChoiceField(
queryset=None,
to_field_name='name',
required=False,)
def __init__(self, *args, **kwargs):
super(CustomForm, self).__init__(*args, **kwargs)
self.fields['name'].queryset = Users.objects.all()

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.

ValueError when overriding the choices for a ForeignKey field in a model's admin

I'm overriding the admin form of a model to modify the choices of a ForeignKey field.
When selecting a choice in the admin form, and saving, I get a ValueError:
Cannot assign "u'6'": "MyModel1.mymodel2" must be a "MyModel2" instance
where 6 is the id of the selected choice.
The new choices is built as ((<choice_1_id>, <choice_1_label>), (<choice_2_id>, <choice_2_label>),...), and I get the same html for the rendered select widget as if I don't modify the choices (apart from the ordering of course).
If I comment self.fields['mymodel2'] = forms.ChoiceField(choices=choices) in MyModel1AdminForm.__init__() I get no error...
Anybody could help?
models.py
class MyModel1(models.Model):
mymodel2 = ForeignKey(MyModel2)
# more fields...
admin.py
class MyModel1AdminForm(forms.ModelForm):
class Meta:
model = MyModel1
def __init__(self, *args, **kwargs):
super(MyModel1AdminForm, self).__init__(*args, **kwargs)
# create choices with ((<choice_1_id>, <choice_1_label>), (<choice_2_id>, <choice_2_label>),...)
self.fields['mymodel2'] = forms.ChoiceField(choices=choices, widget=SelectWithDisabled) # http://djangosnippets.org/snippets/2453/
class MyModel1Admin(admin.ModelAdmin):
form = MyModel1AdminForm
my_site.register(MyModel1, MyModel1Admin)
mymodel2 is the Foreign Key field. You need to supply the queryset if you want to change the choices instead of adding your custom choices:
self.fields['mymodel2'].queryset = MyModel2.objects.all()
If you need to construct the choices manually something like
choices = MyModel2.objects.values_list('pk', 'title')
should work with a standard ChoiceField, where title is the field of the model you want to use as label/verbose name for your choice.
Looking at the snippet you are using values_list won't work so you could fallback to a list comprehension:
[(c.pk, {'label': c.title, 'disabled': False}) for c in MyModel2.objects.all()]
Though you obviously need some more logic to decide whether a choice is enabled or disabled.
I ended up overriding ModelChoiceField:
class MyModelChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
level = getattr(obj, obj._mptt_meta.level_attr)
return {'label': obj.name), 'disabled': check_disabled(obj)}

Django - Show BooleanField in a formset as one group of radio buttons

I have the following models:
class Profile(models.Model):
verified = models.BooleanField(default=False)
def primary_phone(self):
return self.phone_set.get(primary=True)
class Phone(models.Model):
profile = models.ForeignKey(Profile)
type = models.CharField(choices=PHONE_TYPES, max_length=16)
number = models.CharField(max_length=32)
primary = models.BooleanField(default=False)
def save(self, force_insert=False, force_update=False, using=None):
if self.primary:
# clear the primary attribute of other phones of the related profile
self.profile.phone_set.update(primary=False)
self.save(force_insert, force_update, using)
I'm using Phone in a ModelForm as a formset. What I'm trying to do is to show Phone.primary as a radio button beside each instance of Phone. If I make primary as a RadioSelect widget:
class PhoneForm(ModelForm):
primary = forms.BooleanField(widget=forms.RadioSelect( choices=((0, 'False'), (1, 'True')) ))
class Meta:
from accounts.models import Phone
model = Phone
fields = ('primary', 'type', 'number', )
It will show two radio buttons, and they will be grouped together next to each instance. Instead, I'm looking for a way to show only one radio button next to each instance (which should set primary=True for that instance), and have all the set of radio buttons grouped together so that only one of them can be chosen.
I'm also looking for a clean way of doing this, I can do most of the above manually - in my head - but I'm interested to see if there is a better way to do it, a django-style way.
Anyone got an idea?
Ok you've got two dilemma's here. First is you need to group all radio selects from different formsets by giving them the same HTML name attribute. I did that with the add_prefix override below.
Then you have to make sure that the 'primary' field in your post data returns something meaningful, from which you can determine which phone was selected (there should only be one 'name' value in POST data b/c you can only select one radio button from within a group). By assigning the proper prefix value (this needs to be done under _init_ so you can access the self instance), you'll be able to associate the 'primary' value with the rest of its form data (through a custom save method).
I tested a formset with the following and it spat out the right html. So give this a try:
class PhoneForm(ModelForm):
def __init__ (self, *args, **kwargs)
super(PerstransForm, self).__init__(*args, **kwargs)
self.fields['primary'] = forms.BooleanField( widget = forms.RadioSelect(choices=((self.prefix, 'This is my Primary Phone'),))
#enter your fields except primary as you had before.
def add_prefix(self, field):
if field == 'primary': return field
else: return self.prefix and ('%s-%s' % (self.prefix, field)) or field

Django Admin Drop down selections

I use the django admin for updating various data on the MySQL database. I use the basic django admin for this. When entering in new data, I would like to be able to have it so people can only select from a few options to enter in new text data.
For example:
The table holds colors, so instead of letting the admin person (data entry individual in our case) just enter in anything into the text box, how can I get the django admin to only give them several options to choose from?
This can be done via the model field argument choices
myfield = models.CharField(max_length=256, choices=[('green', 'green'), ('red', 'red')])
The only problem with this is that if you already have a value in the database that doesn't match one of these, django might just default it to one of the choices.
If that's a problem and you want to preserve those values, I might override the admin form and either only supply the ChoiceField on add operations or dynamically add whatever is in the DB as one of the valid choices.
class MyForm(ModelForm):
MY_CHOICES = [('green', 'green'), ('red', 'red')]
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
if self.instance.id:
CHOICES_INCLUDING_DB_VALUE = [(self.instance.field,)*2] + self.MY_CHOICES
self.fields['my_field'] = forms.ChoiceField(
choices=CHOICES_INCLUDING_DB_VALUE)
class MyAdmin(admin.ModelAdmin):
form = MyForm
It's better to define a "models.CharField" field type in your model
your_choices = (
('1', u'yello'),
('2', u'red'),
('3', u'black'),
)
types = models.CharField(max_length=32, choices=your_choices, null=True, blank=True)
Then in "admin.py" you should add this new field in your custom admin class like below.
class YourModelAdmin(admin.ModelAdmin):
fields = ('types', )
admin.site.register(YourModel, YourModelAdmin)

Free-form input for ForeignKey Field on a Django ModelForm

I have two models related by a foreign key:
# models.py
class TestSource(models.Model):
name = models.CharField(max_length=100)
class TestModel(models.Model):
name = models.CharField(max_length=100)
attribution = models.ForeignKey(TestSource, null=True)
By default, a django ModelForm will present this as a <select> with <option>s; however I would prefer that this function as a free form input, <input type="text"/>, and behind the scenes get or create the necessary TestSource object and then relate it to the TestModel object.
I have tried to define a custom ModelForm and Field to accomplish this:
# forms.py
class TestField(forms.TextInput):
def to_python(self, value):
return TestSource.objects.get_or_create(name=value)
class TestForm(ModelForm):
class Meta:
model=TestModel
widgets = {
'attribution' : TestField(attrs={'maxlength':'100'}),
}
Unfortunately, I am getting: invalid literal for int() with base 10: 'test3' when attempting to check is_valid on the submitted form. Where am I going wrong? Is their and easier way to accomplish this?
Something like this should work:
class TestForm(ModelForm):
attribution = forms.CharField(max_length=100)
def save(self, commit=True):
attribution_name = self.cleaned_data['attribution']
attribution = TestSource.objects.get_or_create(name=attribution_name)[0] # returns (instance, <created?-boolean>)
self.instance.attribution = attribution
return super(TestForm, self).save(commit)
class Meta:
model=TestModel
exclude = ('attribution')
There are a few problems here.
Firstly, you have defined a field, not a widget, so you can't use it in the widgets dictionary. You'll need to override the field declaration at the top level of the form.
Secondly get_or_create returns two values: the object retrieved or created, and a boolean to show whether or not it was created. You really just want to return the first of those values from your to_python method.
I'm not sure if either of those caused your actual error though. You need to post the actual traceback for us to be sure.
TestForm.attribution expects int value - key to TestSource model.
Maybe this version of the model will be more convenient for you:
class TestSource(models.Model):
name = models.CharField(max_length=100, primary_key=True)
Taken from:
How to make a modelform editable foreign key field in a django template?
class CompanyForm(forms.ModelForm):
s_address = forms.CharField(label='Address', max_length=500, required=False)
def __init__(self, *args, **kwargs):
super(CompanyForm, self).__init__(*args, **kwargs)
try:
self.fields['s_address'].initial = self.instance.address.address1
except ObjectDoesNotExist:
self.fields['s_address'].initial = 'looks like no instance was passed in'
def save(self, commit=True):
model = super(CompanyForm, self).save(commit=False)
saddr = self.cleaned_data['s_address']
if saddr:
if model.address:
model.address.address1 = saddr
model.address.save()
else:
model.address = Address.objects.create(address1=saddr)
# or you can try to look for appropriate address in Address table first
# try:
# model.address = Address.objects.get(address1=saddr)
# except Address.DoesNotExist:
# model.address = Address.objects.create(address1=saddr)
if commit:
model.save()
return model
class Meta:
exclude = ('address',) # exclude form own address field
This version sets the initial data of the s_address field as the FK from self, during init , that way, if you pass an instance to the form it will load the FK in your char-field - I added a try and except to avoid an ObjectDoesNotExist error so that it worked with or without data being passed to the form.
Although, I would love to know if there is a simpler built in Django override.