I can I add fields to a model formset? It seems you can add fields if you user normal formset but not with model formsets (at least it's not the same way). I don't think I should use inline formset either ..?
I want to let users edit their photoalbum (django-photologue). So far I've manage to do this:
PhotoFormSet = modelformset_factory(Photo,
exclude=(
'effect',
'caption',
'title_slug',
'crop_from',
'is_public',
'slug',
'tags'
))
context['gallery_form'] = PhotoFormSet(queryset=self.object.gallery.photos.all())
The problem is that I have to add a checkbox for each photo saying "Delete this photo" and a radio select saying "Set this to album cover".
Thanks in advance!
You can add fields. Just define a form in the normal way, then tell modelformset_factory to use that as the basis for the formset:
MyPhotoForm(forms.ModelForm):
delete_box = forms.BooleanField()
class Meta:
model = Photo
exclude=('effect',
'caption',
'title_slug',
'crop_from',
'is_public',
'slug',
'tags'
))
PhotoFormSet = modelformset_factory(Photo, form=MyPhotoForm)
Related
So to make the above possible I have found out that I have to have ManytoMany Field that is not a problem.
That field is in the form as follows:
class Form(forms.ModelForm):
class Meta:
model = MyModel
fields = ['notes', 'scan']
widgets = {
'scan': forms.CheckboxSelectMultiple(),
}
In the view I have this then:
form = Form(request.POST)
if from.is_valid():
inst = from.save(commit=False)
inst.something = something
inst.save()
Now what do I do, to save the test or scan from the form?
I tried :
inst.test.add(form.cleaned_data['test'])
But that doesn't work for test or scan.
The Model looks like this:
class MyModel(models.Model):
id = models.AutoField(primary_key=True)
notes = models.TextField(default='')
scan = models.ManyToManyField(Scan)
....
Please help I wasn't able find anything in the Internet about this
Thanks!
The documentation of the Form's save method tells it all: If you have a ModelForm that contains the model's ManyToManyField like this:
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ['__all__'] # or fields = ['scans'] assuming scans is the M2M field in MyModel
Then you have two ways to save the relationships:
Directly, using form.save()
Calling save_m2m() is only required if you use save(commit=False). When you use a simple save() on a form, all data – including many-to-many data – is saved without the need for any additional method calls.
Or indirectly because you want to manipulate the instance before saving:
if form.is_valid():
instance = form.save(commit=False)
instance.some_field = some_value
instance.save()
form.save_m2m() # this saves the relationships
I have a many to many field linked with my model1. Now, I created a form for this model1 and added this many to many field as a form field and used FilteredSelectMultiple widget to edit this. Now, the problem is the related many to many field has a soft delete option, which I am tracking with active field in the model2. So, now in the form all the objects are displayed even if they are deleted, is there any way I can show the objects which have active as true in this form field.
My model form looks as follows:
class Editform(form.ModelForm):
class Media:
css = ..
js = ..
class Meta:
Model = model1
fields = [ "x", "y", "ManytoManyfield"]
widgets = {
'ManytoManyfield': FilteredSelectMultiple("Displaay name", False)
}
This answer is close to what you want. I think this may work.
You create an extra field in your ModelForm, populating it with a query.
class Editform(form.ModelForm):
many_to_many_field_active = forms.ChoiceField(choices=[(m2m.id, m2m.name) for m2m in Model2.objects.filter(active=True)])
class Meta:
#...
widgets = {
'many_to_many_field_active': Select(attrs={'class': 'select'}),
I solved this using the multiplechoicefield in my model form as follows.
def __init__(self, *args, **kwargs):
many_to_m_initial = kwargs['instance'].model2.all()
choices = [(m2m.id, m2m.name) for m2m in Model2.objects.filter(active=True)]
self.fields['my_field'] = forms.MultipleChoiceField(choices = choices, widget=FilteredSelectMultiple("verbose name", is_stacked=False, choices=choices))
self.initial['my_field'] = [ m2m.pk fpr m2m in many_to_m_initial ]
I want to create hidden fields that are pre or post populated using the generic CreateView but as far as I can see I only have two choices that do not require specifying a form.
This will display the fields prepopulated:
class FootCreate(CreateView):
model = Footprint
fields = ["source","size","notes", "parent", "created_by"]
success_url = reverse_lazy('home')
def get_initial(self):
parent = Object.objects.get(id=self.kwargs['obj_id'])
return { 'parent': parent, 'created_by': self.request.user }
Or remove the fields I don't want to display from the fields list:
fields = ["source","size","notes"]
but now the fields are not populated on the form so it doesn't validate.
I know I can handle this by subclassing the form or using javascript to hide the fields on the form but I wondered if there is an option to have the equivalent of:
fields = ["source","size","notes", "parent", "created_by"]
hidden_fields = ["parent", "created_by"]
within the view?
ANSWER
No - But a custom model form takes only a few lines. Give full list of fields in the view.:
class FootprintForm(ModelForm):
class Meta:
model = Footprint
widgets = {'created_by': forms.HiddenInput, "object": forms.HiddenInput}
As I now there is no way to do what you want in CBV and I think there is no reason to do it in the View. The form logic should be encapsulated in the Form and you can do it easy with ModelForm and few lines of code
Looking for info how django formsets validation works, though it is more complicated than it sounds. I have a formset with values, part of these values can be inserted there by javascript (it means they do not exist in database yet).
class RequireOneFormSet(BaseInlineFormSet):
def clean(self):
if any(self.errors):
return
form_count = len([f for f in self.forms if f.cleaned_data])
if form_count < 1:
raise ValidationError(_('At least one %(object)s is required.') %
{'object':
_(self.model._meta.object_name.lower())})
class VariantInline(admin.StackedInline):
model = Variant
extra = 1
formset = RequireOneFormSet
class ProductAdmin(admin.ModelAdmin):
class Meta:
model = Product
class Media:
js = (os.path.join(STATIC_URL, 'js', 'admin_utils.js'), )
exclude = ('slug',)
filter_horizontal = ('category',)
inlines = [ImageInline, DetailInline, VariantInline]
manufacturer = ModelChoiceField(Manufacturer.objects.all())
list_filter = ('name', 'manufacturer', 'category')
list_display = ('name', 'manufacturer')
search_fields = ('name',)
save_as = True
Next, basing on those entries I`d like to create objects during formset validation. Django complains that there is no such object in DB when 'Save' button is clicked.
I have tried to override clean method of model, clean of ModelAdmin, save_formset of formset but with no luck as these values created by javascript are filtered out earlier in process. I am looking for info which method takes care of that, and can it be overriden?
EDIT:
Added some code, used view is a generic one from Django.
I`ve managed to resolve it. Key was to create my own field and override clean() method there. As you can see in file django/forms/models.py in class ModelMultipleChoiceField clean() is responsible for checking send values.
class DetailsField(ModelMultipleChoiceField):
def clean(self, value):
(code here)
class VariantForm(ModelForm):
details = DetailsField(queryset=Detail.objects.all(),
widget=FilteredSelectMultiple('details', False))
class VariantInline(admin.StackedInline):
model = Variant
extra = 1
formset = RequireOneFormSet
form = VariantForm
When I render my formset, one of the field renders as a select box because it is a foreign field in the model. Is there a way to change this to a text input? I want to populate that field by using Ajax auto complete. Adding a widget to the modelform is not working because the modelformset_factory takes a model and not a model form.
EDIT
My Model Form
class RecipeIngredientForm(ModelForm):
class Meta:
model = RecipeIngredient
widgets = { 'ingredient' : TextInput(), }
I use it in my view
RecipeIngredientFormSet = modelformset_factory(RecipeIngredient, form=RecipeIngredientForm)
objRecipeIngredients = RecipeIngredientFormSet()
EDITED MODEL FORM
class RecipeIngredientForm(ModelForm):
ingredient2 = TextInput()
class Meta:
model = RecipeIngredient
I create the form set like this
RecipeIngredientFormSet = modelformset_factory(RecipeIngredient, form=RecipeIngredientForm)
objRecipeIngredients = RecipeIngredientFormSet()
QUESTION
Do I have to use the formset in html? Can I just hard code the fields that get generated and using javascript I can create new fields and increment the "form-TOTAL-FORMS"? If I can then I do not have to worry about my model form.
Thanks
modelformset_factory does take a form. Here's the function signature from django.forms.models:
def modelformset_factory(
model, form=ModelForm, formfield_callback=lambda f: f.formfield(),
formset=BaseModelFormSet,
extra=1, can_delete=False, can_order=False,
max_num=0, fields=None, exclude=None):
If this isn't working for you, show some code and I'll try and see what is going wrong.
Edit after various comments As you point out, the widget argument is buggy when used in this way. So the solution is not to use it - it's a very recent addition in any case. Instead, define the field directly on the form:
class RecipeIngredientForm(forms.ModelForm):
ingredient = forms.ModelChoiceField(widget=forms.TextInput))
class Meta:
model = RecipeIngredient