If I create a formset using modelformset_factory like this:
IngredientFormSet = modelformset_factory(RecipeIngredients, form=RecipeIngredientsForm)
formset = IngredientFormSet(request.POST)
and my form looks like this
class RecipeIngredientsForm(forms.ModelForm):
Ingredient = forms.CharField(max_length= 100)
class Meta:
model = RecipeIngredients
exclude = ('weightmetric','recipe')
Where would I put my custom .save() method? Would I put it under the RecipeIngredientsForm?
[a potential solution]
In your view do something like this:
if formset.is_valid():
for form in formset:
obj = form.save(commit=False) #obj = RecipeIngredient model object
try:
ingredient_in_db = Ingredient.objects.get(name = form.cleaned_data.get('ingredientform'))
except:
ingredient_in_db = None
if ingredient_in_db:
obj.ingredient = ingredient_in_db
else:
new_ingredient = Ingredient.objects.create(name = form.cleaned_data.get('ingredientform'))
obj.ingredient = new_ingredient
obj.recipe = recipeobj
obj.save()
Incidentally, I think this method would also allow me to do a custom .save(), given that I take each form in the formset and do a form.save(commit= False) on it. It was easier though, to just do it in my view, since I needed access to the recipe object.
I'm not sure of the best way to override the existing save() method on the BaseModelForm. My guess is that you actually would want to either:
Create your own field that has it's own to_python() method which will take the text and find/create the associated object.
class IngredientField(forms.CharField):
def to_python(self, value):
# Lookup model or create new one
ingredient = models.Ingredient.objects.get(name=value)
if not ingredient:
ingredient = models.Ingredient.create(name=value)
return ingredient
Use save(commit=False) on the formset, do the ingredient lookup/creation, and then commit afterwards
Related
I am trying to solve one issue about saving data in db.
This is an example how I think of it:
class MyModel(models.Model):
id = models.AutoField(primary_key=True)
fieldX = models.SomeFieldType()
#property:
def foo(self):
return self._foo
#foo.setter
def foo(self, var):
self._foo=var
class MyModelForm(models.Modelform):
class Meta:
model = models.MyModel
fields = '__all__'
The thing is I have dict that I am passing to this form (so I am not using view or any provided interaction with user directly. In dict I have some fields and what I want to do is one field that is passed to use it as model property but I do not want it to be saved in db.
So I tried something like:
form = MyModelForm(data_dict)
if form.is_valid():
form.foo = data_dict['data_for_property_not_db']
form.save()
Form does not know that my model has this property.
So basiclly what I want is to write some parts of my data_dict normaly to form and db as always ->works fine
and then I want some data_info pass to that property and use it somewhere in save() as needed without saving it to db itself.
Can you help me with this?
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 an object (myModel) which i want to create according to a form(myModelForm), I also want that the object would be related to a requiered ForeignKey object (Group) which i want to decide by myself.(i.e, don't want it to be in the form.
So if I try to use form.save() i get an error. is there a way i can add Group ForeignKey (in view) before i use save()?
My code looks something like this:
class myModel(models.Model):
myGroup = ForeignKey(Group)
normal_field1 = TextField()
...
normal_field2 = TextField()
class myModelForm(ModelForm):
class Meta:
model = myModel
fields = [normal_field1,noraml_field2]
muchas gracias
Use commit=False to create the item without persisting it to the database.
if form.is_valid():
obj = form.save(commit=False)
obj.myGroup= whatever
obj.save()
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
All,
I am trying to initialize a modelformset with a manytomanyfield. A catch is that I do not know in advance the name of the manytomanyfield (nor the class it is bound to).
Here are my models and forms:
class Book_model(models.Model):
title = models.CharField(max_length=BIG_STRING)
authors = models.ManyToManyField("Author_model",)
class Author_model(models.Model):
name = models.CharField(max_length=BIG_STRING)
class Book_form(ModelForm):
class Meta:
model = Book_model
class Author_form(ModelForm:
class Meta:
model = Author_model
Author_formset = modelformset_factory(Author_model,form=Author_form)
And elsewhere in my code I am trying to display a Model_form along with an Author_formset. When it comes time to initialize that formset, though, I'm not sure what to do. At that point I know the name of the m2m field ("authors"), the parent model instance (Book_model), the parent form instance (Book_form), and the formset class (Author_formset). I assume that I just need to do something like this:
m2m_field = getattr(book,"authors")
qset = field.filter(<only authors for which there is a m2m relationship from this book>)
formset = Author_formset(queryset=qset)
But, I don't know the right terms to put in the filter.
Any suggestions?
You're on the right track.
book.authors is the queryset of "authors for which there is a m2m from this book". So that is perfectly valid to pass into the formset init.
formset = AuthorFormset(queryset=m2m_field.all())
I think I have solved this.
In theory this is the correct way to do things, as Daniel suggests:
formset = Author_formset(queryset=book.authors.all())
But I can't do that directly, because I am trapped in some generic code that could be called for any model/form/formset. So I'm forced to do this instead:
# these 4 lines are just for clarity's sake
# I don't actually know what these map to in my code
MyModelClass = Book_model
MyFormClass = Book_form
MyFormSetClass = Author_formset
fieldName = "authors"
def DoStuff(model_id=None):
if (model_id):
model = MyModelClass.objects.get(pk=model_id)
else:
model = MyModelClass()
form = MyFormClass(instance=model)
if model.pk:
m2mModels = getattr(model,fieldName)
formset = MyFormSetClass(queryset = m2mModels.all())
else:
m2mModelClass = MyFormSetClass.form.Meta.model
formset = MyFormSetClass(queryset = m2mModelClass.objects.none())
This seems a bit ugly, but it works.