Django modelForm update certain fields - django

I have a 'Farm' model and a corresponding ModelForm as follows:
class FarmForm(ModelForm):
class Meta:
model = Farm
fields = ['farm_name','address','farm_size', 'latitude', 'longitude']
I can save a new Farm object through my client app (it requires that I fill in all the fields mentioned in my ModelForm).
I want to have another view where in I can update an existing Farm where the user can perhaps insert/update only those fields he/she wants to change. I tried something like following by passing only one of the field values through Postman but it gives me Form_not_valid error:
#api_view(['POST'])
def updateFarm(request, farmId):
farm = Farm.objects.get(id=farmId)
form = FarmForm(instance=farm, data=request.POST)
if form.is_valid():
farm = form.save()
farm = Farm.objects.filter(id=farm.id)
serializer = FarmSerializer(farm, many=True)
return JSONResponse(serializer.data)
#return Response("Data saved")
else:
return Response("Form not valid, insert correct fields.")
How can I build my view that let's user update only those fields he thinks are relevant? My url: url(r'^farms/update/(?P<farmId>\d\d)/$', views.updateFarm),

You can generate a boolean hidden form field for every field in your model, that gets set when a field is modified. For example name input:
<input id="id_name" maxlength="100" name="name" type="text">
will be followed by a name__specified hidden input:
<input id="id_name__specified" name="name__specified" type="hidden">
You track changes to field name with some js (very easy with plain js or jquery) and update name__specified accordingly to true/false.
In order to do this automatically and be able to re-use it, you can abstract this in a base form class and keep your form simple:
class BaseForm(forms.ModelForm):
suffix = '__specified'
def __init__(self, **kwargs):
super(BaseForm, self).__init__(**kwargs)
fields = list(self.fields)
for f in fields:
# Set the field default value from the instance
self.fields[f].widget.attrs['default'] = getattr(self.instance, f)
# JS tracking field changes
js = """
document.getElementById("id_%s").value =
this.value != this.getAttribute("default");
""" % (f + self.suffix)
self.fields[f].widget.attrs['onchange'] = js
self.fields[f + self.suffix] = forms.BooleanField(
widget=forms.HiddenInput(),
required=False
)
def clean(self):
data = super(BaseForm, self).clean()
flags = [f for f in self.fields if self.suffix in f]
for x in flags:
specified = data.get(x, False)
if not specified:
field = x[:-len(self.suffix)]
# If not specified grab it's current value from the instance
data[field] = getattr(self.instance, field)
# If the form validation complains that it's missing
# clear the error since we are not changing it's value
if field in self.errors:
del self.errors[field]
return data
So your modified form:
class FarmForm(BaseForm):
class Meta:
model = Farm
fields = ['farm_name','address','farm_size', 'latitude', 'longitude']
Note, you should pass the instance when instantiating a form in your GET function or simply inherit your view from UpdateView so that will be handled automatically:
class MyView(UpdateView):
template_name = 'my_template.html'
form_class = FarmForm
queryset = Farm.objects.all()
Now you can do partial updates!

Related

Looking for format for KeywordsField.save_form_data

I have a Mezzanine Project and am trying to update the keywords on a blog entry. I am having difficulty getting the format correct to call KeywordsField.save_form_data this invokes a js that will update the keywords on a blog post. See below:
From Messanine/generic/fields.py
class KeywordsField(BaseGenericRelation):
"""
Stores the keywords as a single string into the
``KEYWORDS_FIELD_NAME_string`` field for convenient access when
searching.
"""
default_related_model = "generic.AssignedKeyword"
fields = {"%s_string": CharField(editable=False, blank=True,
max_length=500)}
def __init__(self, *args, **kwargs):
"""
Mark the field as editable so that it can be specified in
admin class fieldsets and pass validation, and also so that
it shows up in the admin form.
"""
super(KeywordsField, self).__init__(*args, **kwargs)
self.editable = True
def formfield(self, **kwargs):
"""
Provide the custom form widget for the admin, since there
isn't a form field mapped to ``GenericRelation`` model fields.
"""
from mezzanine.generic.forms import KeywordsWidget
kwargs["widget"] = KeywordsWidget
return super(KeywordsField, self).formfield(**kwargs)
def save_form_data(self, instance, data):
"""
The ``KeywordsWidget`` field will return data as a string of
comma separated IDs for the ``Keyword`` model - convert these
into actual ``AssignedKeyword`` instances. Also delete
``Keyword`` instances if their last related ``AssignedKeyword``
instance is being removed.
"""
from mezzanine.generic.models import Keyword
related_manager = getattr(instance, self.name)
# Get a list of Keyword IDs being removed.
old_ids = [str(a.keyword_id) for a in related_manager.all()]
new_ids = data.split(",")
removed_ids = set(old_ids) - set(new_ids)
# Remove current AssignedKeyword instances.
related_manager.all().delete()
# Convert the data into AssignedKeyword instances.
if data:
data = [related_manager.create(keyword_id=i) for i in new_ids]
# Remove keywords that are no longer assigned to anything.
Keyword.objects.delete_unused(removed_ids)
super(KeywordsField, self).save_form_data(instance, data)
From my Views.py
class PubForm(forms.ModelForm):
class Meta:
model = BlogPost
fields = ['keywords']
def UpdatePub(request, slug):
blog_post = BlogPost.objects.get(id=slug)
if request.method == 'POST':
form = PubForm(request.POST)
if form.is_valid():
publish_date = datetime.datetime.now()
blog_post.status = CONTENT_STATUS_PUBLISHED
publish_date=publish_date
tags=form.cleaned_data['keywords']
blog_post.save()
KeywordsField.save_form_data(user,blog_post,tags)
return HttpResponseRedirect('/write/')
else:
form = PubForm(instance=blog_post)
return render(request, 'blog_my_pub.html', {'form' : form})
It complains that the field 'user' has no attribute 'name'. I have tried many different values for this parameter and cannot figure it out. Any help would be appreciated.
Thanks for any input.

Django - This is field is required error

I am new to Django and trying to save some data from the form to the model. I want to insert into two models which have a foreign key constraint relationship (namely Idea and IdeaUpvotes) i.e. from a html template to a view.
My submit code is:
def submitNewIdea(request):
#get the context from the request
context = RequestContext(request)
print(context)
#A HTTP POST?
if request.method == 'POST':
form = submitNewIdeaForm(request.POST)
# Have we been provided with a valid form?
if form.is_valid():
# Save the new Idea to the Idea model
print(request.POST.get("IdeaCategory"))
print(request.POST.get("IdeaSubCategory"))
i = Idea( idea_heading = form["idea_heading"].value()
,idea_description = form["idea_description"].value()
,idea_created_by = form["idea_created_by"].value()
,idea_votes = form["idea_votes"].value()
,idea_category = request.POST.get("IdeaCategory") #value from dropdown
,idea_sub_category = request.POST.get("IdeaSubCategory") #value from dropdown
)
i.save()
# get the just saved id
print(Idea.objects.get(pk = i.id))
iu = IdeaUpvotes(idea_id = Idea.objects.get(pk = i.id)
,upvoted_by = form["upvoted_by"].value()
,upvoted_date = timezone.now() )
iu.save()
form.save(commit = True)
# Now call the index() view.
# The user will be shown the homepage.
return index(request)
else:
# The supplied form contained errors - just print them to the terminal.
print (form.errors)
else:
# If the request was not a POST, display the form to enter details.
form = submitNewIdeaForm()
# Bad form (or form details), no form supplied...
# Render the form with error messages (if any).
return render(request,'Ideas/Index.html',{'form' :form})
form.py --->
class submitNewIdeaForm(forms.ModelForm):
idea_heading = forms.CharField(label = "idea_heading",max_length =1000,help_text= "Please enter the idea heading.")
idea_description= forms.CharField(label = "idea_description",max_length =1000,help_text= "Please enter the idea description.",widget=forms.Textarea)
idea_created_by=forms.CharField(max_length =200, widget = forms.HiddenInput(), initial='wattamw')
idea_votes = forms.IntegerField(widget=forms.HiddenInput(), initial=1)
upvoted_by=forms.CharField(max_length =200, widget = forms.HiddenInput(), initial='abcde')
"""
#commented code
#idea_category_name = forms.CharField(label = "idea_category_name",max_length =250,help_text= "Please select an Idea Category.")
#idea_sub_category = forms.CharField(label = "idea_sub_category",max_length =250,help_text= "Please select an Idea Sub Category.")
idea_category_name = forms.ModelChoiceField(
queryset = IdeaCategory.objects.all(),
widget=autocomplete.ModelSelect2(url='category-autocomplete'))
idea_sub_category = forms.ModelChoiceField(
queryset = IdeaSubCategory.objects.all(),
widget = autocomplete.ModelSelect2(
url='subcategory-autocomplete',
forward = (forward.Field('idea_category_name','id'),)))
"""
class Meta:
model = Idea
fields = ('idea_heading','idea_description','idea_created_by','idea_votes','idea_category_name','idea_sub_category')
class Meta:
model = IdeaUpvotes
fields = ('upvoted_by',)
def __init__(self,*args,**kwargs):
super(submitNewIdeaForm,self).__init__(*args,**kwargs)
self.fields['idea_category_name'] = forms.ModelChoiceField(
queryset = IdeaCategory.objects.all(),
widget=autocomplete.ModelSelect2(url='category-autocomplete'))
self.fields['idea_sub_category'] = forms.ModelChoiceField(
queryset = IdeaSubCategory.objects.all(),
widget = autocomplete.ModelSelect2(
url='subcategory-autocomplete',
forward = (forward.Field('idea_category_name','id'),)))
I am able to print the values and see that they are passed,but I still get the following error :
Error Description
I have removed any foreign key references to the table, the fields are simple character fields.
Please help me out.
Thanks.
In the first place, your form validation is failing. It seems to me that your form template may be wrong.
The second thing is that you don't use Django forms properly. All you need to do to achieve the functionality you are looking for is to use ModelForm and let the form's save method to create the object for you. All you need to do is:
Associate your SubmitNewIdeaForm with the Idea model:
# forms.py
class SubmitNewIdeaForm(ModelForm):
class Meta:
model = Idea
fields = (
'idea_heading',
'idea_description',
'idea_created_by',
'idea_votes',
'idea_category',
'idea_sub_category'
)
Render the form
#form_template.html
<form action="{% url 'your_url' %}" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit" />
</form>
Finally jsut check if the form is valid and call form.save() like so:
def submitNewIdea(request):
if form.is_valid():
form.save()
That's it! I hope that I helped you.
Cheers!
Finished = models.IntegerField('Finished percentage', error_messages={'required':''})
Worked for me.

Django: how to validate inlineformset against another related form

I have looked around quite a bit but can't quite figure out how to make this work. I basically have a Document form and an Item inlineformset in a view, and I need to perform some validation dependent on the field values in each form. For example, if the Item's copyright_needed field is YES then the Document's account field is required.
How can I pass a reference to the Document form, so that inside ItemForm's clean method, I can look at the Document form's cleaned_data? I'm trying to use curry, as I've seen recommended in other SO answers, but it's not working quite right.
Models.py
class Document(models.Model):
account = models.CharField(max_length=22, blank=True, null=True)
class Item(models.Model):
copyright_needed = models.CharField(max_length=1)
# Document foreign key
document = models.ForeignKey(Document)
It's the ItemForm clean method that shows what I'd like to accomplish, and the error I'm getting.
Forms.py -- EDIT - added init to ItemForm
class ItemForm(forms.ModelForm):
class Meta:
model = Item
fields=[..., 'copyright_needed' ]
def __init__(self, *args, **kwargs):
self.doc_form = kwargs.pop('doc_form')
super(ItemForm, self).__init__(*args, **kwargs)
def clean(self):
cleaned_data = super(ItemForm, self).clean()
msg_required = "This field is required."
cr = cleaned_data.get("copyright_needed")
# This line generates this error: DocumentForm object has no attribute cleaned_data
acct_num = self.doc_form.cleaned_data.get("account")
if cr and cr == Item.YES:
if not acct_num:
self.doc_form.add_error("account", msg_required)
return cleaned_data
class DocumentForm(forms.ModelForm):
...
account = forms.CharField(widget=forms.TextInput(attrs={'size':'25'}), required=False)
class Meta:
model = Document
fields = [ ..., 'account' ]
views.py
def create_item(request):
# create empty forms
form=DocumentForm()
ItemFormSet = inlineformset_factory(Document, Item,
form=ItemForm,
can_delete=False,
extra=1 )
# This is my attempt to pass the DocumentForm to each ItemForm, but its not working
ItemFormSet.form = staticmethod(curry(ItemForm, doc_form=form))
item_formset=ItemFormSet(instance=Document())
if request.POST:
d = Document()
form=DocumentForm(request.POST, instance=d)
if form.is_valid():
new_document=form.save(commit=False)
item_formset=ItemFormSet(request.POST, instance=new_document)
if item_formset.is_valid():
new_document.save()
new_item=item_formset.save()
return HttpResponseRedirect(...)
item_formset=ItemFormSet(request.POST)
return render(request,...)
I'm not even sure what the view is doing - it looks like you're confused on the role of the inlineformset and curry. Firstly, you're currying the init method of ItemForm with the doc_form, but you haven't written an init.
Secondly, it looks like you want to be able to edit the Items inside the Document form. So you need the modelformset_factory, and pass in a custom Formset, on which you write a clean method, that has access to everything you need.
from django.forms.models import modelformset_factory
ItemFormSet = modelformset_factory(Item, form=ItemForm, formset=MyCustomFormset)
then in your customformset -
class MyCustomFormset(BaseInlineFormset):
def clean():
super(MyCustomFormset, self).clean()
for form in self.forms:
#do stuff
Note the clean method on each ItemForm has already been called - this is similar to writing your own clean() on a normal modelform.
EDIT:
OK, so ignore the formset clean, I misunderstood. Just make your document form in the view, pass it along with the formset, then put them all in the same form tag.
<form method="post" action=".">
{%for field in doc_form %}
{{field}}
{%endfor%}
{%for form in formset%}
{{form.as_p}}
{%endfor%}
</form>
Then you have access to all the fields in your request.POST, and you can do whatever you want
doc_form = DocumentForm(request.POST)
formset = ItemFormSet(request.POST)
if all([doc_form.is_valid(), formset.is_valid()]):
#do some stuff

Django forms dynamic generation and validation

Let's say I have the following model
class Foo(models.Model):
title = models.CharField(default='untitled')
# Bar is a MPTT class, so I'm building a tree here
# Should not matter for my question...
root = models.ForeignKey(Bar)
leaf = models.ForeignKey(Bar)
To create new Foo objects I want to make use of a ModelForm like this:
class FooForm(ModelForm):
# possibly custom validation functions needed here...
class Meta:
model = Foo
fields = '__all__'
My view looks like this:
def create(request, leaf_id=None):
form = FooForm(data=request.POST or None)
if form.is_valid():
new = form.save()
return redirect('show.html', root_id=new.root.id)
return render('create_foo.html',
{ 'form': form })
As you can see, the view function should be used to handle two different use-cases:
/foo/create/
/foo/create/4where 4 is a leaf-ID.
If the leaf-ID is given, the form obviously isn't required to show a form field for this. Furthermore, root can be determined from leaf, so it isn't required aswell.
I know that I can dynamically change the used widgets, so I can switch them to HiddenInput, but I would like to not even show them as hidden to the user. But if I dynamically exclude them, they are not available for form validation and the whole process will fail during the validation process.
What I would like to achieve is: Show only form fields to the user, that are not yet pre-filled. Is there any best-practice available for this case?
You can do that by overriding the __init__() method of FooForm.
We override the __init__() method and check if instance argument was passed to the form. If instance was passed, we disable the root and leaf form fields so that it is not displayed in the template.
We will pass instance argument to the form when the request is of type foo/create/4 i.e. leaf_id is not None.
forms.py
class FooForm(ModelForm):
def __init__(self, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs) # call the 'super()' init method
instance = getattr(self, 'instance', None) # get the `instance` form attribute
if instance and instance.id: # check if form has 'instance' attribute set and 'instance' has an id
self.fields['root'].widget.attrs['disabled'] = 'disabled' # disable the 'root' form field
self.fields['leaf'].widget.attrs['disabled'] = 'disabled' # disable the 'leaf' form field
# custom validation functions here
....
class Meta:
model = Foo
fields = '__all__'
In our view, we first check if leaf_id argument was passed to this view. If leaf_id was passed,we retrieve the Foo object having leaf id as the leaf_id. This instance is then passed when initializing a form and is updated when form.save() is called. We will use the instance to populate the form with values as the attributes set on the instance.
If leaf_id is not passed, then we initialize FooForm with data argument.
views.py
def create(request, leaf_id=None):
# Get the instance if any
instance = None
if leaf_id:
instance = Foo.objects.get(leaf_id=leaf_id) # get the 'Foo' instance from leaf_id
# POST request handling
if request.method=='POST':
if instance:
form = FooForm(data=request.POST, instance=instance) # Populate the form with initial data and supply the 'instance' to be used in 'form.save()'
else:
form = FooForm(data=request.POST)
if form.is_valid():
new = form.save()
return redirect('show.html', root_id=new.root.id)
return render('create_foo.html',
{ 'form': form })
# GET request handling
if instance:
form = FooForm(initial=instance._data, instance=instance) # form will be populated with instance data
else:
form = FooForm() # blank form is initialized
return render('create_foo.html',
{ 'form': form })

Django form. How to post a string in form and save it as objects id?

I have a model for adverts which has a relation to Towns model. This model contains a list of towns which have some meta data.
In my form I've implemented ajax autocomplete for towns. Each town has a name_unique field and based on this data autocomplete helps with filling the input form.
However, I actually need a relationship to Town.id not Town.name_unique.
How I can perform such action so django form will accept a name_unique value and save it as actual 'id' of town?
How to post in form a string and save it as
class Advert(models.Model):
class Meta:
verbose_name = u"Ogłoszenie"
verbose_name_plural = u"Ogłoszenia"
ordering = ['-date_added', ]
title = models.CharField(verbose_name="Tytuł ogłoszenia", max_length=32)
text = models.TextField(verbose_name="Treść ogłoszenia", max_length=3000)
location = models.ForeignKey("division.Towns", verbose_name="Miejscowość")
class AdvertForm(ModelForm):
category = CustomTreeNodeChoiceField(queryset=Category.objects.filter(parent__isnull=False),
empty_label="Wybierz kategorię", label="Kategoria")
class Meta:
model = Advert
exclude = ('ip', 'user', 'first_name', 'last_name')
widgets = {
'location': TextInput
}
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(AdvertForm, self).__init__(*args, **kwargs)
def add(request):
form = AdvertForm(request.POST or None, request=request)
if form.is_valid():
advert = form.save(commit=False)
advert.save()
return HttpResponseRedirect(reverse('adverts.views.detail', kwargs={'pk': advert.pk}))
return TemplateResponse(request, "adverts/add.html", {'form': form, })
I've used the JQuery-Autocomplete for that and combined that with a custom FormField/Widget. Basicly the widget renders two input-fields, one hidden containing the id and one visible containing the text-representation and the autocomplete-logic:
<input type="text" class="ac_input" name="%(name)s_text" id="%(html_id)s_text" value="%(text)s"/>
<input type="hidden" name="%(name)s" id="%(html_id)s" value="%(value)s" />
If the autocomplete-field is changed, it loads a dictionary from the server in the form of [{id: "..", text:""}, ...] and sets the text-field to contain the value of text and the hidden id-field to id. This way the hidden id-field is used by the form and it contains the id you want.
I uploaded my code to a pastebin (link: http://pastebin.com/LncqfQM2). The code is a bit older and the comments are half-missing, half-german, sorry :/
In the form i use:
ort = AutocompleteModelChoiceField(Ort.objects, url=reverse("orte-autocompletecallback"))
And in the View:
def callback(request):
# some code loading the objects
return [{'id': row.pk, 'label':row.name} for row in objects]
I hope this helps.
edit: I started reworking bits of the code (Tidy it up a bit, comments, examples). If im finished i post another link in / edit the old link.