Using Modelform with ModelChoicefield does not work for me, gives undefined error when submitting the form - django

I am trying to use a form that adds data to the model RaportProductie using AJAX.
In the form I have 2 dropdown inputs that take data from the ManoperaRaportareBloc model.
These are the attributes from ManoperaRaportareBloc : categorie_lucrare and subcategorie_lucrare
When I submit the form it shows an error with undefined.
Please help.
ty.
forms.py:
class RaportProductieForm(forms.ModelForm):
data = forms.DateField(initial=datetime.date.today)
categorie_lucrare = forms.ModelChoiceField(queryset=ManoperaRaportareBloc.objects.all().values_list('categorie_lucrare', flat=True))
subcategorie_lucrare = forms.ModelChoiceField(queryset=ManoperaRaportareBloc.objects.all().values_list('subcategorie_lucrare', flat=True))
class Meta:
model = RaportProductie
fields = ['lucrare', 'data', 'tip', 'subcontractor', 'obiectiv', 'categorie_lucrare', 'subcategorie_lucrare', 'um', 'cantitate', 'valoare_prod']
views.py:
def raportproductie_create_view(request):
# request should be ajax and method should be POST.
if request.is_ajax and request.method == "POST":
# get the form data
form = RaportProductieForm(request.POST)
# save the data and after fetch the object in instance
if form.is_valid():
instance = form.save()
# serialize in new friend object in json
ser_instance = serializers.serialize('json', [ instance, ])
# send to client side.
return JsonResponse({"instance": ser_instance}, status=200)
else:
# some form errors occured.
data = {
'result': 'error',
'message': 'Form invalid',
'form': 'oops.'
}
return JsonResponse(data, status=400)
# some error occured
return JsonResponse({"error": ""}, status=400)
template.html:
$("#friend-form").submit(function (e) {
// preventing from page reload and default actions
e.preventDefault();
// serialize the data for sending the form data.
var serializedData = $(this).serialize();
console.log(serializedData)
// make POST ajax call
$.ajax({
type: 'POST',
url: "{% url 'proiecte:raportprod-create' %}",
data: serializedData,
success: function (response) {
// display the newly friend to table.
var instance = JSON.parse(response["instance"]);
var fields = instance[0]["fields"];
$("#table-ajax tbody").prepend("<tr><td>"+fields.data+"</td><td>"+fields.tip+"</td><td>"+fields.subcontractor+"</td><td>"+fields.obiectiv+"</td><td>"+fields.categorie_lucrare+"</td><td>"+fields.subcategorie_lucrare+"</td><td>"+fields.um+"</td><td>"+fields.cantitate+"</td><td>"+fields.valoare_prod+"</td></tr>")
},
error: function (xhr, status, error) {
var err = JSON.parse(xhr.responseText);
alert(err.error);
}
})
})
later edit:
i've used pdb to debug, printed the form before checking if valid and it returns this:
form.data
<QueryDict: {'csrfmiddlewaretoken': ['*********'], 'lucrare': ['1'], 'date': ['2023-01-10'], 'tip': ['1'], 'subcontractor': ['TGC Tadjiki'], 'obiectiv': ['obiectiv'], 'categorie_lucrare': ['CONFECTII_METALICE'], 'subcategorie_lucrare': ['CONSTRUCTIE ATIC - CONF METALICA'], 'um': ['km'], 'cantitate': ['2'], 'valoare_prod': ['0']}>
so...the inputs are working,
also in the ajax code, i've also gave a console.log(serializedData) and it outputs this:
csrfmiddlewaretoken=***********=1&date=2023-01-10&tip=1&subcontractor=TGC%20Tadjiki&obiectiv=obiectiv&categorie_lucrare=HIDRO_TERASE&subcategorie_lucrare=CONSTRUCTIE%20ATIC%20-%20CONF%20METALICA&um=mp.&cantitate=2&valoare_prod=0
later later edit:
when I am not using ModelChoiceField in the forms.py, and write the inputs by hand, the form submits...

I found an answer to my question, in the Modelform modified the custom queryset so that they remain Charfield and have added choices:
class RaportProductieForm(forms.ModelForm):
date = forms.DateField(initial=datetime.date.today)
queryset=ManoperaRaportareBloc.objects.all()
OPTIONS1 = [(choice.pk, choice.categorie_lucrare) for choice in queryset]
OPTIONS2 = [(choice.pk, choice.subcategorie_lucrare) for choice in queryset]
queryset2 = Echipa.objects.all()
OPTIONS3 = [(choice.pk, choice.nume) for choice in queryset2]
categorie_lucrare = forms.CharField(widget=forms.Select( choices = OPTIONS1 ))
subcategorie_lucrare = forms.CharField(widget=forms.Select( choices = OPTIONS2 ))
subcontractor = forms.CharField(widget=forms.Select( choices = OPTIONS3 ))
class Meta:
model = RaportProductie
fields = ['lucrare', 'date', 'tip', 'subcontractor', 'obiectiv', 'categorie_lucrare', 'subcategorie_lucrare', 'um', 'cantitate', 'valoare_prod']

Related

How to process two GET requests, at the same time and collect the data in javascript

Hello I have a question and a doubt, I am in a view, when I give a button I do the following code
function pagar_recibo(id) {
$.ajax ({
delay: 250,
type: 'GET',
url: '/general/recibo/add',
data: {
'id': id,
'action_al': 'add_alumnos',
},
});
};
This button is a href that goes to another url, that is to say it makes another request to the server with GET then in the new view in the get_context_data I have the following
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['arg'] = json.dumps(self.alum())
# context['arg'] = self.alum()
return context
and the function is as follows
def alum(self, **kwargs):
data = []
try:
action = self.request.GET.get('action_al', False)
print(action)
if action == False:
return
elif action == 'add_alumnos':
data = []
id = int(self.request.GET['id'])
Alumnos = Alumno.objects.filter(pk=id)
for i in Alumnos:
item = i.toJSON()
item['text'] = i.get_full_name()
data.append(item)
print(data)
except Exception as e:
data['error'] = str(e)
return data
This is how I pick it up in the template
window.data = {{ arg|safe }};
The problem is that one GET request is empty, the url change and the other is the ajax request with the given key and value, but I always get empty windows data, how could I fix that?
It could be done with a POST request? then how do I collect the value in the js file?
Please I would need help. Regards

Testing AJAX in Django

I want to test an AJAX call in my Django app.
What is does is adding a product to a favorite list. But I can't find a way to test it.
My views.py:
def add(request):
data = {'success': False}
if request.method=='POST':
product = request.POST.get('product')
user = request.user
splitted = product.split(' ')
sub_product = Product.objects.get(pk=(splitted[1]))
original_product = Product.objects.get(pk=(splitted[0]))
p = SavedProduct(username= user, sub_product=sub_product, original_product = original_product)
p.save()
data['success'] = True
return JsonResponse(data)
My html:
<form class="add_btn" method='post'>{% csrf_token %}
<button class='added btn' value= '{{product.id }} {{ sub_product.id }}' ><i class=' fas fa-save'></i></button
My AJAX:
$(".row").on('click', ".added", function(event) {
let addedBtn = $(this);
console.log(addedBtn)
event.preventDefault();
event.stopPropagation();
var product = $(this).val();
console.log(product)
var url = '/finder/add/';
$.ajax({
url: url,
type: "POST",
data:{
'product': product,
'csrfmiddlewaretoken': $('input[name=csrfmiddlewaretoken]').val()
},
datatype:'json',
success: function(data) {
if (data['success'])
addedBtn.hide();
}
});
});
The problem is that I pass '{{product.id }} {{ sub_product.id }}' into my views.
My test so far:
class Test_add_delete(TestCase):
def setUp(self):
self.user= User.objects.create(username="Toto", email="toto#gmail.com")
self.prod = Product.objects.create(
name=['gazpacho'],
brand=['alvalle'],
)
self.prod_2 = Product.objects.create(
name=['belvita'],
brand=['belvita', 'lu', 'mondelez'],
)
def test_add(self):
old_saved_products = SavedProduct.objects.count()
user = self.user.id
original_product = self.prod.id
sub_product = self.prod_2.id
response = self.client.post(reverse('finder:add', args=(user,))), {
'product': original_product, sub,product })
new_saved_products = SavedProducts.objects.count()
self.assertEqual(new_saved_products, old_saved_products + 1)
My test is not running and I get a SyntaxError 'product': original_product, sub_product. I know it's not the proper way to write it but my AJAX send the two ids with a space in between to the view.
If all you want to do is test if the data was actually saved, instead of just returning data['success'] = True you can return the whole entire new object... That way you can get back the item you just created from your API, and see all the other fields that may have been auto-gen (ie date_created and so on). That's a common thing you'll see across many APIs.
Another way to test this on a Django level is just to use python debugger
import pdb; pdb.set_trace() right before your return and you can just see what p is.
The set_trace() will stop python and give you access to the code scope from the command line. So just type 'l' to see where you are, and type(and hit enter) anything else that's defined, ie p which will show you what p is. You can also type h for the help menue and read the docs here

JSON Serializing in Django and JSON Parsing in Jquery

So I have this code:
def success_comment_post(request):
if "c" in request.GET:
c_id = request.GET["c"]
comment = Comment.objects.get(pk=c_id)
model = serializers.serialize("json", [comment])
data = {'message': "Success message",
'message_type': 'success',
'comment': model }
response = JSONResponse(data, {}, 'application/json')
return response
else:
data = {'message': "An error occured while adding the comment.",
'message_type': 'alert-danger'}
response = JSONResponse(data, {}, 'application/json')
and back in jQuery I do the following:
$.post($(this).attr('action'), $(this).serialize(), function(data) {
var comment = jQuery.parseJSON(data.comment)[0];
addComment($("#comments"), comment);
})
Now... in the Django function, why do I have to put the comment in [] -->
model = serializers.serialize("json", [comment])
and back in jQuery, why do I have to do jQuery.parseJSON(data.comment)[0]?
Anyway I don't have to do this? I find it weird I have to hardcode the [0]
Thanks a lot!
Well serializers.serialize only takes querysets or iterators with django model instances but using Comment.objects.get will return an object and not an iterator and that is why you will need to put it in [] to make it an iterator.
Since its a list you will have to access it like an array in javascript too. I would suggest not using serializer and using simplejson to convert field values to json.
Sample Code:
from django.utils import simplejson as json
from django.forms.models import model_to_dict
comment = Comment.objects.get(pk=c_id)
data = {'message': "Success message",
'message_type': 'success',
'comment': model_to_dict(comment)}
return HttpResponse(json.dumps(data), mimetype='application/json')
I have only mentioned relevant parts of your code. Hopefully this should solve your problem

form throwing a internal server error on submit with ajax, django

I have a form that is throwing a internal server error when i submit it with data
it says it is due to an integrity error:
IntegrityError at /cookbook/createrecipe/
(1048, "Column 'original_cookbook_id' cannot be null")
what is weird is that i dont have a column original_cookbook_id - what i do have is an original_cookbook that is a foreign key to a cookbook model
here is the view that is giving the error:
def createrecipe(request):
print "entering createrecipeview"
if request.method == 'POST':
print "form is a post"
form = RecipeForm(request.POST)
print form.errors
if form.is_valid():
print "form is valid"
form = RecipeForm(initial = {'original_cookbook' : request.user.cookbooks.all()[0]})// this is where the error is being thrown
form.save()
t = loader.get_template('cookbook/create_form.html')
c = RequestContext(request, {
'form': form,
})
data = {
'replace': True,
'form': t.render(c),
'success': True,
}
json = simplejson.dumps(data)
return HttpResponse(json, mimetype='text/plain')
else:
print "form is invalid"
form = RecipeForm(request.POST)
t = loader.get_template('cookbook/create_form.html')
c = RequestContext(request, {
'form':form,
})
data ={
'form': t.render(c),
'success': False,
}
json = simplejson.dumps(data)
return HttpResponse(json, mimetype='text/plain')
i think i might be declaring the original_cookbook the wrong way
anyone else have any experience with _id being appended to a foreign key/ have any idea how i might be able to resolve this issue
thanks a lot
katie
update
here is my javascript function
<script type="text/javascript">
$(document).ready(function(){
function hijack() {
var form = $('form#createrecipeform');
form.each (function(){
this.reset();
});
form.submit(function(e) {
e.preventDefault();
console.log('ajax form submission function called successfully.');
//form = $(this);
console.log(form)
var serialized_form = form.serialize();
$.ajax({ type: "POST",
url: $(this).attr('action'),
data: serialized_form,
success: (function(data) {
console.log('ajax success function called successfully.');
data = $.parseJSON(data);
if (data.success) {
console.log('success');
//right now it is logging success to
//console but nothing else is happening
//what else should happen on data success?
} else {
console.log('failure');
var newForm = data.form;
form.replaceWith(newForm);
hijack();
}
})
});
return false;
});
};
hijack();
});
</script>
You do have an original_cookbook_id field. That's where the pk for the foreign key is store in the table. Django automagically appends the _id bit. All the error means is that that field is set to NOT NULL, and you didn't provide a value to original_cookbook to fill it in with.
UPDATE
There's nothing "wrong" per se, but a few things to note. First, initial only sets the initial value that appears in the form (obviously enough). However, if it's later set back to empty by the user or some other action, it's still empty, i.e. initial has no true effect on the eventual posted data.
You didn't post your javascript, so I can't speak to how you're submitting the form, but that too could potentially be an issue. If you're not submitting the field with the rest of the AJAX post, for whatever reason, it doesn't matter what the actual field may or may not be set to.

Django formset unit test

I can't run a unit test with formset.
I try to do a test:
class NewClientTestCase(TestCase):
def setUp(self):
self.c = Client()
def test_0_create_individual_with_same_adress(self):
post_data = {
'ctype': User.CONTACT_INDIVIDUAL,
'username': 'dupond.f',
'email': 'new#gmail.com',
'password': 'pwd',
'password2': 'pwd',
'civility': User.CIVILITY_MISTER,
'first_name': 'François',
'last_name': 'DUPOND',
'phone': '+33 1 34 12 52 30',
'gsm': '+33 6 34 12 52 30',
'fax': '+33 1 34 12 52 30',
'form-0-address1': '33 avenue Gambetta',
'form-0-address2': 'apt 50',
'form-0-zip_code': '75020',
'form-0-city': 'Paris',
'form-0-country': 'FRA',
'same_for_billing': True,
}
response = self.c.post(reverse('client:full_account'), post_data, follow=True)
self.assertRedirects(response, '%s?created=1' % reverse('client:dashboard'))
and I have this error:
ValidationError: [u'ManagementForm data is missing or has been
tampered with']
My view :
def full_account(request, url_redirect=''):
from forms import NewUserFullForm, AddressForm, BaseArticleFormSet
fields_required = []
fields_notrequired = []
AddressFormSet = formset_factory(AddressForm, extra=2, formset=BaseArticleFormSet)
if request.method == 'POST':
form = NewUserFullForm(request.POST)
objforms = AddressFormSet(request.POST)
if objforms.is_valid() and form.is_valid():
user = form.save()
address = objforms.forms[0].save()
if url_redirect=='':
url_redirect = '%s?created=1' % reverse('client:dashboard')
logon(request, form.instance)
return HttpResponseRedirect(url_redirect)
else:
form = NewUserFullForm()
objforms = AddressFormSet()
return direct_to_template(request, 'clients/full_account.html', {
'form':form,
'formset': objforms,
'tld_fr':False,
})
and my form file :
class BaseArticleFormSet(BaseFormSet):
def clean(self):
msg_err = _('Ce champ est obligatoire.')
non_errors = True
if 'same_for_billing' in self.data and self.data['same_for_billing'] == 'on':
same_for_billing = True
else:
same_for_billing = False
for i in [0, 1]:
form = self.forms[i]
for field in form.fields:
name_field = 'form-%d-%s' % (i, field )
value_field = self.data[name_field].strip()
if i == 0 and self.forms[0].fields[field].required and value_field =='':
form.errors[field] = msg_err
non_errors = False
elif i == 1 and not same_for_billing and self.forms[1].fields[field].required and value_field =='':
form.errors[field] = msg_err
non_errors = False
return non_errors
class AddressForm(forms.ModelForm):
class Meta:
model = Address
address1 = forms.CharField()
address2 = forms.CharField(required=False)
zip_code = forms.CharField()
city = forms.CharField()
country = forms.ChoiceField(choices=CountryField.COUNTRIES, initial='FRA')
In particular, I've found that the ManagmentForm validator is looking for the following items to be POSTed:
form_data = {
'form-TOTAL_FORMS': 1,
'form-INITIAL_FORMS': 0
}
Every Django formset comes with a management form that needs to be included in the post. The official docs explain it pretty well. To use it within your unit test, you either need to write it out yourself. (The link I provided shows an example), or call formset.management_form which outputs the data.
It is in fact easy to reproduce whatever is in the formset by inspecting the context of the response.
Consider the code below (with self.client being a regular test client):
url = "some_url"
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
# data will receive all the forms field names
# key will be the field name (as "formx-fieldname"), value will be the string representation.
data = {}
# global information, some additional fields may go there
data['csrf_token'] = response.context['csrf_token']
# management form information, needed because of the formset
management_form = response.context['form'].management_form
for i in 'TOTAL_FORMS', 'INITIAL_FORMS', 'MIN_NUM_FORMS', 'MAX_NUM_FORMS':
data['%s-%s' % (management_form.prefix, i)] = management_form[i].value()
for i in range(response.context['form'].total_form_count()):
# get form index 'i'
current_form = response.context['form'].forms[i]
# retrieve all the fields
for field_name in current_form.fields:
value = current_form[field_name].value()
data['%s-%s' % (current_form.prefix, field_name)] = value if value is not None else ''
# flush out to stdout
print '#' * 30
for i in sorted(data.keys()):
print i, '\t:', data[i]
# post the request without any change
response = self.client.post(url, data)
Important note
If you modify data prior to calling the self.client.post, you are likely mutating the DB. As a consequence, subsequent call to self.client.get might not yield to the same data, in particular for the management form and the order of the forms in the formset (because they can be ordered differently, depending on the underlying queryset). This means that
if you modify data[form-3-somefield] and call self.client.get, this same field might appear in say data[form-8-somefield],
if you modify data prior to a self.client.post, you cannot call self.client.post again with the same data: you have to call a self.client.get and reconstruct data again.
Django formset unit test
You can add following test helper methods to your test class [Python 3 code]
def build_formset_form_data(self, form_number, **data):
form = {}
for key, value in data.items():
form_key = f"form-{form_number}-{key}"
form[form_key] = value
return form
def build_formset_data(self, forms, **common_data):
formset_dict = {
"form-TOTAL_FORMS": f"{len(forms)}",
"form-MAX_NUM_FORMS": "1000",
"form-INITIAL_FORMS": "1"
}
formset_dict.update(common_data)
for i, form_data in enumerate(forms):
form_dict = self.build_formset_form_data(form_number=i, **form_data)
formset_dict.update(form_dict)
return formset_dict
And use them in test
def test_django_formset_post(self):
forms = [{"key1": "value1", "key2": "value2"}, {"key100": "value100"}]
payload = self.build_formset_data(forms=forms, global_param=100)
print(payload)
# self.client.post(url=url, data=payload)
You will get correct payload which makes Django ManagementForm happy
{
"form-INITIAL_FORMS": "1",
"form-TOTAL_FORMS": "2",
"form-MAX_NUM_FORMS": "1000",
"global_param": 100,
"form-0-key1": "value1",
"form-0-key2": "value2",
"form-1-key100": "value100",
}
Profit
There are several very useful answers here, e.g. pymen's and Raffi's, that show how to construct properly formatted payload for a formset post using the test client.
However, all of them still require at least some hand-coding of prefixes, dealing with existing objects, etc., which is not ideal.
As an alternative, we could create the payload for a post() using the response obtained from a get() request:
def create_formset_post_data(response, new_form_data=None):
if new_form_data is None:
new_form_data = []
csrf_token = response.context['csrf_token']
formset = response.context['formset']
prefix_template = formset.empty_form.prefix # default is 'form-__prefix__'
# extract initial formset data
management_form_data = formset.management_form.initial
form_data_list = formset.initial # this is a list of dict objects
# add new form data and update management form data
form_data_list.extend(new_form_data)
management_form_data['TOTAL_FORMS'] = len(form_data_list)
# initialize the post data dict...
post_data = dict(csrf_token=csrf_token)
# add properly prefixed management form fields
for key, value in management_form_data.items():
prefix = prefix_template.replace('__prefix__', '')
post_data[prefix + key] = value
# add properly prefixed data form fields
for index, form_data in enumerate(form_data_list):
for key, value in form_data.items():
prefix = prefix_template.replace('__prefix__', f'{index}-')
post_data[prefix + key] = value
return post_data
The output (post_data) will also include form fields for any existing objects.
Here's how you might use this in a Django TestCase:
def test_post_formset_data(self):
url_path = '/my/post/url/'
user = User.objects.create()
self.client.force_login(user)
# first GET the form content
response = self.client.get(url_path)
self.assertEqual(HTTPStatus.OK, response.status_code)
# specify form data for test
test_data = [
dict(first_name='someone', email='someone#email.com', ...),
...
]
# convert test_data to properly formatted dict
post_data = create_formset_post_data(response, new_form_data=test_data)
# now POST the data
response = self.client.post(url_path, data=post_data, follow=True)
# some assertions here
...
Some notes:
Instead of using the 'TOTAL_FORMS' string literal, we could import TOTAL_FORM_COUNT from django.forms.formsets, but that does not seem to be public (at least in Django 2.2).
Also note that the formset adds a 'DELETE' field to each form if can_delete is True. To test deletion of existing items, you can do something like this in your test:
...
post_data = create_formset_post_data(response)
post_data['form-0-DELETE'] = True
# then POST, etc.
...
From the source, we can see that there is no need include MIN_NUM_FORM_COUNT and MAX_NUM_FORM_COUNT in our test data:
MIN_NUM_FORM_COUNT and MAX_NUM_FORM_COUNT are output with the rest of the management form, but only for the convenience of client-side code. The POST value of them returned from the client is not checked.
This doesn't seem to be a formset at all. Formsets will always have some sort of prefix on every POSTed value, as well as the ManagementForm that Bartek mentions. It might have helped if you posted the code of the view you're trying to test, and the form/formset it uses.
My case may be an outlier, but some instances were actually missing a field set in the stock "contrib" admin form/template leading to the error
"ManagementForm data is missing or has been tampered with"
when saved.
The issue was with the unicode method (SomeModel: [Bad Unicode data]) which I found investigating the inlines that were missing.
The lesson learned is to not use the MS Character Map, I guess. My issue was with vulgar fractions (¼, ½, ¾), but I'd assume it could occur many different ways. For special characters, copying/pasting from the w3 utf-8 page fixed it.
postscript-utf-8