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
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 trying to set the min_value Attribute on Form level within my modelform.
Forms.py
class ProductForm(forms.models.ModelForm):
class Meta:
model = Artikel
localized_fields = '__all__'
fields = ('price',)
Model.py
class Artikel(models.Model):
price = models.FloatField(help_text ='Price')
How can i setup the modelform that i can constrain the values allowed on the modelform?
I want the user to only enter values greater than or equal to 0.01.
I dont want to restricted on Database Level cause i dont want to limit myself in that regard.
You can override the ModelForm's init method. This will set the min attribute on the field to 10:
def __init__(self, *args, **kwargs):
super(ProductForm, self).__init__(*args, **kwargs)
self.fields['price'].widget.attrs['min'] = 10
In addition to setting 'min' attribute on widget, also override form's clean_fieldname() method:
class ProductForm(forms.models.ModelForm):
def __init__(self, *args, **kwargs):
super(ProductForm, self).__init__(*args, **kwargs)
self.fields['price'].widget.attrs['min'] = 0.01
def clean_price(self):
price = self.cleaned_data['price']
if price < 0.01:
raise forms.ValidationError("Price cannot be less than 0.01")
return price
class Meta:
model = Artikel
localized_fields = '__all__'
fields = ('price',)
Doc says:
The clean_<fieldname>() method is called on a form subclass – where is replaced with the name of the form field attribute. This method does any cleaning that is specific to that particular attribute, unrelated to the type of field that it is. This method is not passed any parameters. You will need to look up the value of the field in self.cleaned_data and remember that it will be a Python object at this point, not the original string submitted in the form (it will be in cleaned_data because the general field clean() method, above, has already cleaned the data once).
The simple way to do this is to set the validator on the field and provide a custom error message:
class ProductModelForm(forms.ModelForm):
price = forms.FloatField(min_value=0.01,
error_messages={'min_value': u'Price cannot be less than 0.01'})
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 want to change formfield widget depend on other field value. Default is select because model field is foreign key. My models are as follows:
class ProductFeatureValue(BaseName):
feature = models.ForeignKey('ProductTypeFeature')
class Meta:
verbose_name = _(u'Product Feature Value')
verbose_name_plural = _(u'Product Feature Values')
class ProductFeature(models.Model):
product = models.ForeignKey('Product')
feature = models.ForeignKey('ProductTypeFeature')
value = models.ForeignKey('ProductFeatureValue')
And my form is as follows:
class ProductFeatureFormForInline(ModelForm):
class Meta:
model = ProductFeature
def __init__(self,*args,**kwargs):
super(ProductFeatureFormForInline,self).__init__(*args,**kwargs)
if isinstance(self.instance,ProductFeature):
try:
widget_type = self.instance.feature.product_type.producttypefeature_set.all()[0].widget #TODO Fix that 0 slice
if widget_type == u'TEXT':
self.fields['value'] = CharField(widget=TextInput())
if widget_type == u'MULTIPLE_SELECT':
self.fields['value'].widget = MultipleChoiceField()
except:
pass
It changes the fields widget but when it make it charfield and populate it with instance it shows the id of the model not the value (author : 1) and it makes sense to show it that way, but i want to show(author: Dan Brown).
I have tried with initial values but not working. Any tips of doing that will be highly appreciated. Thanks
Your __unicode__() method on the model should dictate what is shown there, if I'm not missing something.
On your model:
class ProductFeatureValue(BaseName):
[... snipped code ...]
def __unicode__():
return self.feature
This snippet assumes that self.feature is what you want to return, and not something else on the parent BaseName.
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})