I have the following Django models:
class Contact(models.Model):
id #primary key
#Other contact info
class ContactType(models.Model):
contacttype_choices=(('Primary', 'Primary'),
('Billing', 'Billing'),
('Business', 'Business'),
('Technology', 'Technology'))
contact=models.ForeignKey(Contact)
type=models.CharField(choices=contacttype_choices, max_length=30)
class Meta:
unique_together=('contact', 'type')
So any contact object can have up to four contact types, with each type either being present or absent. I would like to make a Model Form for Contact with a multiple choice field for contact type. How can I populate this contact type field with the existing values when I construct the Contact form with a Contact instance?
Edit: To clarify, I want one checkbox to be created for each of those four choices, and if the form is instantiated with a model instance, then I want the checkboxes to be checked for each of the related objects that exists, similar to what happens automatically with the rest of the fields.
I would probably structure the models as such, so I can choose which contact type when creating/editing the Contact. If ContactType is related as a ManyToMany, we automatically get a ModelMultpleChoiceField in the ModelForm, and all we need to do is use the CheckboxSelectMultiple widget to get the output you're looking for.
When we pass an instance of Contact to the ContactForm, Django will bind any pre-selected ContactType choices and check the checkboxes for us.
Setting the title of each ContactType to unique does the same as unique_together on the contact and contact_type.
#models.py
class Contact(models.Model):
#other fields
contact_types = models.ManyToMany(ContactType)
class ContactType(models.Model):
title = models.CharField(max_length=20, unique=true)
#forms.py
class ContactForm(forms.ModelForm):
class Meta:
model = Contact
def __init__(self, *args, **kwargs):
super(ContactForm, self).__init__(*args, **kwargs)
self.fields['contact_types'].widget = forms.CheckboxSelectMultiple()
Have you tried something like this?
class ContactForm(forms.ModelForm):
class Meta:
model = Contact
def __init__(self, *args, **kwargs):
super(ContactForm, self).__init__(*args, **kwargs)
self.fields['contacttypes'] = forms.MultipleChoiceField(
choices = CONTACT_TYPE_CHOICES,
label = "contact type",
widget = CheckBoxSelectMultiple
)
contact = Contact.objects.get(pk=1) # or whatever
types = ContactType.objects.filter(contact = contact)
form = ContactForm(instance=contact, initial={'contacttypes' : types})
Related
I would like to have a form with the preselected checkboxes of a ManyToManyField.
models.py
class Store(models.Model):
...
class Brand(models.Model):
stores = models.ManyToManyField(Store, blank=True, related_name="brands")
forms.py
class StoreForm(ModelForm):
class Meta:
model = Store
fields = ('brands',)
I get this exception:
django.core.exceptions.FieldError: Unknown field(s) (brands) specified for Store
I know that I can add the field manually to the class:
brands = forms.ModelMultipleChoiceField(
queryset=Brand.objects.all(),
widget=forms.CheckboxSelectMultiple,
)
If I do this the checkboxes are not preselected.
How is it possible to include the ManyToMany field from "the other side" of the model (from Store)?
#hedgie To change the field in the other model is not a good option for me because I use it already.
But the __init__() was a good hint. I come up with this solution and it seems to work.
class StoreForm(ModelForm):
def __init__(self, *args, **kwargs):
if kwargs.get('instance'):
brand_ids = [t.pk for t in kwargs['instance'].brands.all()]
kwargs['initial'] = {
'brands': brand_ids,
}
super().__init__(*args, **kwargs)
# https://stackoverflow.com/questions/49932426/save-many-to-many-field-django-forms
def save(self, commit=True):
# Get the unsaved Pizza instance
instance = forms.ModelForm.save(self, False)
# Prepare a 'save_m2m' method for the form,
old_save_m2m = self.save_m2m
def save_m2m():
old_save_m2m()
# This is where we actually link the pizza with toppings
instance.brands.clear()
for brand in self.cleaned_data['brands']:
instance.brands.add(brand)
self.save_m2m = save_m2m
# Do we need to save all changes now?
# Just like this
# if commit:
instance.save()
self.save_m2m()
return instance
brands = forms.ModelMultipleChoiceField(
queryset=Brand.objects.all(),
widget=forms.CheckboxSelectMultiple,
)
Though it seems to be not very elegant. I wonder why django does not support a better way.
One possibility is to define the field on the "other" model. So instead of writing this:
class Store(models.Model):
...
class Brand(models.Model):
stores = models.ManyToManyField(Store, blank=True, related_name="brands")
You can write this:
class Brand(models.Model):
...
class Store(models.Model):
brands = models.ManyToManyField(Brand, blank=True, related_name="stores")
Or, if you have manually added the field to the form, you could populate its initial value in the form's __init__() method.
I am currently using a MultiChoiceField to represent the corresponding ManyToManyField of my field. It works fine.
I updated my form to use the attribute to_field_name in the form field to change the field used for the values of the inputs and it works fine too.
My issue is that Django, to choose the selected data of the form field, is using the pk and not the field given in to_field_name. Any clue ?
class Model1(models.Model):
name = models.Charfield(...)
muscles = models.ManyToManyField(Model2, blank=True, null=True,)
class Model2(models.Model):
name = models.CharField(...)
field1 = models.CharField(...)
class MyForm(ModelForm):
muscles = forms.ModelMultipleChoiceField(
queryset=None,
to_field_name="field1", required=False,)
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['muscles'].queryset = Model2.objects.all()
The field of the model is a standard ManyToManyField.
As a example :
In model2, I have these elements : {pk=1, name=name1, field1=3}, {pk=2, name=name2, field1=1}, {pk=3, name=name3, field1=2}.
the generated select is:
<option value=3>name1</option>
<option value=1>name2</option>
<option value=2>name3</option>
If I select name2, the right pk (ie 2) is saved in database.
When the form is displayed, the selected option is name3 (ie value 2) instead of name1.
I hope I have been clear enough.
Thanks for your help !
It looks as if you might have hit this bug, which is fixed in Django 1.10.
Well, I managed to achieve what I wanted. As suggested by #Alasdair, I created an intermediary model.
class Model1(models.Model):
name = models.Charfield(...)
muscles = models.ManyToManyField(Model2, blank=True, null=True,)
class Model2(models.Model):
name = models.CharField(...)
field1 = models.CharField(...)
class Model1Model2(models.Model):
model1 = models.ForeignKey(Model1)
model2 = models.ForeignKey(Model2)
class MyForm(ModelForm):
muscles = forms.ModelMultipleChoiceField(
queryset=None,
to_field_name="field1", required=False,)
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['muscles'].queryset = Model2.objects.all()
Then in the save_model method, I can go through the values of my field and saves them.
The main point to solve my problem was clearly the intermediary model.
Thanks #Alasdair
I'm using admin.TabularInline in my admin code for which I've made a custom form.
class RateCardForm(forms.ModelForm):
category = forms.ModelChoiceField(queryset=models.Category.objects.all(), label='Category')
class Meta:
model = models.RateCard
fields = ('category')
class RateCardInline(admin.TabularInline):
model = models.RateCard
form = RateCardForm
extra = 3
The problem is that after I've saved my model instance, whenever I edit the model instance, it would remove the pre-selected choice and I'll have to select the choice again. Any ideas as to how to stop it?
Also for ModelChoiceField if I don't specify the label, then it would come up as None on admin page, but I don't need to specify it for admin.StackedInline.
To preselect the currently selected category instance you can set its primary key to the field's initial value by overriding __init__() on the ModelForm:
class RateCardForm(forms.ModelForm):
category = forms.ModelChoiceField(queryset=models.Category.objects.all(), label='Category')
class Meta:
model = models.RateCard
fields = ('category')
def __init__(self, *args, **kwargs):
super(RateCardForm, self).__init__(*args, **kwargs)
instance = kwargs.get('instance')
# Instance will be None for the empty extra rows.
if instance:
selected_pk = # query the primary key of the currently selected category here
self.fields['category'].initial = selected_pk
I would like my data to be editable inline in the Django admin page. However, I only want some fields columns in each row to be editable. These columns will change for each row. Basically, I want a dropdown choice to be displayed if the value in a certain cell is null. If it is not null, then I don't want it to be editable and would like it to be readonly.
models.py:
class Size(models.Model):
size = models.CharField(max_length=20, primary_key=True)
class Book(models.Model):
title = models.CharField(max_length=100, primary_key=True)
size = models.ForeignKey(Size, null=False)
class Pamphlet(models.Model):
title = models.CharField(max_length=100, primary_key=True)
size = models.ForeignKey(Size, null=True)
book = models.ForeignKey(Book, null=True)
admin.py:
class PamphletAdmin(admin.ModelAdmin):
model = Pamphlet
list_editable = ('size','book')
list_display = ('title', 'size', 'book',)
def get_changelist_form(self, request, **kwargs):
return PamphletChangeListForm
forms.py:
class PamphletChangeListForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(PamphletChangeListForm, self).__init__(*args, **kwargs)
instance = kwargs.get('instance')
if instance:
self.fields['book'].queryset = Book.objects.filter(
size=instance.size
)
if instance.size is not None:
self.fields['size'].widget.attrs['readonly'] = 'readonly'
This setup is not working for me. The size shows as editable in the changelist form even when it is not null. Also - what have I failed to understand?
If your field uses an input element, such as a TextField, add the readonly attribute to the field's widget's attrs dict in the changelist form's __init__ method. Something like this:
class PamphletChangeListForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(PamphletChangeListForm, self).__init__(*args, **kwargs)
instance = kwargs.get('instance')
if instance:
self.fields['book'].queryset = Book.objects.filter(
size=instance.size
)
if instance.size is not None:
self.fields['size'].widget.attrs['readonly'] = 'readonly'
That won't protect you against a malicious user faking post data - for that you'd need to customize your admin further. But if you have malicious users on your admin you have bigger problems.
If your field uses a select element, you have to change it more - select attributes don't have readonly as a supported attribute. Instead, you'll want a hidden input with the unchanging value, and a text representation so the user can see what the setting is. Django does not include such a widget, but you can define your own:
class LabeledHiddenInput(forms.widgets.HiddenInput):
def render(self, name, value, attrs=None):
base_output = super(LabeledHiddenInput, self).render(name, value, attrs)
if value:
return base_output + unicode(value)
else:
return base_output
You might need more careful escaping or even some HTML formatting, this is just a quick example. Check the source code for the built in widgets if you need more examples.
Then you can use that widget instead of the default select:
if instance.size is not None:
self.fields['size'].widget = LabeledHiddenInput()
I have a simple form in Django that looks like this:
class SettingForm(forms.Form):
theme = forms.CharField(rrequired=True,
initial='multgi'
)
defaultinputmessage = forms.CharField(required=True,
initial='Type here to begin..'
)
...and the model to store it looks like:
class Setting(models.Model):
name = models.CharField(
null=False, max_length=255
)
value= models.CharField(
null=False, max_length=255
)
When the form is submitted, how can i store the form fields as key value pairs and then when the page is rendered, how can I initialize the form with the key's value. I've tried looking for an implementation of this but have been unable to find one.
Any help?
Thanks.
I'm assuming you want to store 'theme' as the name and the value as the value, same for defaultinputmessage. If that's the case, this should work:
form = SettingForm({'theme': 'sometheme', 'defaultinputmessage': 'hello'})
if form.is_valid():
for key in form.fields.keys():
setting = Setting.objects.create(name=key, value=form.cleaned_data[key])
Here's how I did it.
I needed to do this because I had a Model that stored information as key value pairs and I needed to build a ModelForm on that Model but the ModelForm should display the key-value pairs as fields i.e. pivot the rows to columns. By default, the get() method of the Model always returns a Model instance of itself and I needed to use a custom Model. Here's what my key-value pair model looked like:
class Setting(models.Model):
domain = models.ForeignKey(Domain)
name = models.CharField(null=False, max_length=255)
value = models.CharField(null=False, max_length=255)
objects = SettingManager()
I built a custom manager on this to override the get() method:
class SettingManager(models.Manager):
def get(self, *args, **kwargs):
from modules.customer.proxies import *
from modules.customer.models import *
object = type('DomainSettings', (SettingProxy,), {'__module__' : 'modules.customer'})()
for pair in self.filter(*args, **kwargs): setattr(object, pair.name, pair.value)
setattr(object, 'domain', Domain.objects.get(id=int(kwargs['domain__exact'])))
return object
This Manager would instantiate an instance of this abstract model. (Abstract models don't have tables so Django doesn't throw up errors)
class SettingProxy(models.Model):
domain = models.ForeignKey(Domain, null=False, verbose_name="Domain")
theme = models.CharField(null=False, default='mytheme', max_length=16)
message = models.CharField(null=False, default='Waddup', max_length=64)
class Meta:
abstract = True
def __init__(self, *args, **kwargs):
super(SettingProxy, self).__init__(*args, **kwargs)
for field in self._meta.fields:
if isinstance(field, models.AutoField):
del field
def save(self, *args, **kwargs):
with transaction.commit_on_success():
Setting.objects.filter(domain=self.domain).delete()
for field in self._meta.fields:
if isinstance(field, models.ForeignKey) or isinstance(field, models.AutoField):
continue
else:
print field.name + ': ' + field.value_to_string(self)
Setting.objects.create(domain=self.domain,
name=field.name, value=field.value_to_string(self)
)
This proxy has all the fields that I'd like display in my ModelFom and store as key-value pairs in my model. Now if I ever needed to add more fields, I could simply modify this abstract model and not have to edit the actual model itself. Now that I have a model, I can simply build a ModelForm on it like so:
class SettingsForm(forms.ModelForm):
class Meta:
model = SettingProxy
exclude = ('domain',)
def save(self, domain, *args, **kwargs):
print self.cleaned_data
commit = kwargs.get('commit', True)
kwargs['commit'] = False
setting = super(SettingsForm, self).save(*args, **kwargs)
setting.domain = domain
if commit:
setting.save()
return setting
I hope this helps. It required a lot of digging through the API docs to figure this out.