I've tried all possible solutions on several threads and I'm still unable to fix the problem. I have the following code:
models.py
class CustomerVisit(models.Model):
start_date = models.DateField()
end_date = models.DateField()
customer = models.ForeignKey(Customer)
address = models.ForeignKey(Address)
forms.py
address = forms.ModelChoiceField(label='Address',
queryset=Address.objects.none(),
widget=forms.Select(attrs={'style': 'width: 100%;'}))
customer = forms.ModelChoiceField(label='Customer',
queryset=Customer.objects.all(),
widget=forms.Select(attrs={'style': 'width: 100%;'}))
views.py
if request.method == "POST":
# Cleaning fields
post = request.POST.copy()
post['address'] = Address.objects.get(id=post['address'])
post['start_date'] = dateparser.parse(post['start_date'])
post['end_date'] = dateparser.parse(post['end_date'])
# Updating request.POST
request.POST = post
form = CustomerVisitForm(request.POST)
if form.is_valid():
form.save(commit=True)
return redirect("customervisit:calendar")
js
$("#id_customer").select2({}).on("change", function () {
var customer_id = $("#id_customer").val();
var id_address = $("#id_address");
id_address.select2({
ajax: {
url: '/get_customer_address/' + customer_id,
dataType: "json",
type: "GET",
data: function (params) {
var queryParameters = {
term: params.term
}
return queryParameters;
},
processResults: function (data) {
return {
results: $.map(data, function (item) {
return {
text: item.text,
id: item.id
}
})
};
}
}
});
});
My address select its being populated based on customer selection using ajax call using select2. After reading several threads I noticed that modelchoicefield expects a Address object so that's why I'm using the following code on my view before the form is being validated: post['address'] = Address.objects.get(id=post['address']) but I'm still getting the Select a valid choice. That choice is not one of the available choices. error
I'm using queryset=Address.objects.none(), because I need an empty select
Problem solved.
If someone in the future have the same error as me, checking to_python method from the ModelChoiceField saved my day:
def to_python(self, value):
if value in self.empty_values:
return None
try:
key = self.to_field_name or 'pk'
value = self.queryset.get(**{key: value})
except (ValueError, TypeError, self.queryset.model.DoesNotExist):
raise ValidationError(self.error_messages['invalid_choice'], code='invalid_choice')
return value
So I changed my queryset to queryset=Address.objects instead of queryset=Address.objects.none() or queryset=Address.objects.all()
Thanks Daniel Roseman for your comments
I know what you're trying to achieve, my solution would be to initialise the field as charfield with widget select, and then override the clean method. Example:
class InvoiceForm(forms.ModelForm):
customer = forms.CharField(widget=forms.Select(attrs={'style': 'min-width: 150px;'}))
class Meta:
model = Invoice
exclude = ['id', 'created', 'is_paid']
def clean_customer(self):
customer_id = self.cleaned_data['customer']
customer = Customer.objects.get(id=customer_id)
if customer:
return customer
else:
raise ValidationError("Customer ID invalid")
Related
I have a website that allows users to submit the same form several times (but with different input) but also allows them to view each form they submitted and edit it.
The problem is with the "key_name" field. If I view an already submitted form (edit_keydef) then there is the ValidationError message with the field highlighted in red, when in fact my intention is for that ValidationError to occur only when they are about to submit but have provided the same name as another form. (The view used when filling in the form is different to that when editing the form - as with the editing there's more fields) but both views use the same model.
I don't understand why DJango runs the clean() function when the form has already been sumbitted, i.e. the validation has already occurred and the data has already been submitted
views.py
def edit_keydef(request, id):
data = {'title': 'View or Edit Key Definition'}
template = 'generate_keys/edit_keydef.html'
keydef = get_object_or_404(KeyDefinition, pk=id)
if request.method == "POST":
keydetails_form_instance = KeyDefDetailsForm(request.POST, instance=keydef)
if 'Save' in request.POST:
if keydetails_form_instance.is_valid():
if keydetails_form_instance.cleaned_data['encoded_activation_key']:
activation_obj = Activation()
result = activation_obj.GenerateKey(keydef)
keydetails_form_instance.save()
return redirect('/key/edit/' + str(id))
else:
log.debug(keydetails_form_instance.errors)
if 'Generate' in request.POST:
if keydetails_form_instance.is_valid():
activation_obj = Activation()
result = activation_obj.GenerateKey(keydef)
if "error" in result:
data['error_msg'] = format_html(
'<p>Failed to generate activation key because:</p>'
+ '<p>'
+ str(result['error'])
+ '</p>'
)
else:
keydetails_form_instance.save()
return redirect('/key/edit/' + str(id))
else:
log.debug(keydetails_form_instance.errors)
else:
keydetails_form_instance = None
if not id:
keydetails_form_instance = KeyDefDetailsForm()
else:
# Retrieve a previously saved form.
try:
keydetails = KeyDefinition.objects.get(pk=id)
keydetails_form_instance = KeyDefDetailsForm(keydetails.to_dict(),
instance = keydetails)
except KeyDefinition.DoesNotExist as e:
return redirect('/record_does_not_exist')
# Form instance has been saved at this stage so update the variant list.
data['form'] = keydetails_form_instance
data['set_product_options_url'] = reverse_lazy('set_product_options', kwargs={'id':id})
return render(request, template, data)
models.py
class KeyDefinition (models.Model) :
...
def clean (self) :
....
# ensure that each user can't have two keys with the same name
key_name_exists = KeyDefinition.objects.filter(key_name=self.key_name, developer_email=self.developer_email)
if key_name_exists:
raise ValidationError (
{'key_name' : ['This Key Name already exists']}
)
class Meta:
verbose_name = "Key Definition"
unique_together = [['key_name', 'developer_email']]
forms.py
class KeyDefDetailsForm (ModelForm) :
def __init__(self, *args, **kwargs) :
super(ModelForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.fields['version'].widget.attrs['class'] = "major_minor"
def update_variants(self, keydef_obj):
self.fields.update({
'feature_variant': forms.ModelChoiceField(
widget = Select(),
queryset = FeatureVariant.objects.filter(product__feature_name=keydef_obj.feature),
required = False,
label = "Product"
)
})
def update_available_hosts(self, keydef_obj):
# only show hosts that have been created by this user
self.fields.update({
'available_hosts': forms.ModelChoiceField(
empty_label=None,
initial = AvailableHosts.objects.get(host_label=keydef_obj.host_label).id,
widget=Select(),
queryset = AvailableHosts.objects.filter(host_developer_email=keydef_obj.developer_email),
required = False,
label = "Available Hosts"
)
})
class Meta :
model = KeyDefinition
fields = '__all__'
widgets = {
'customer_name' : TextInput(),
'host_method' : TextInput(),
'issue_date' : TextInput(attrs={'type':'date'}),
'expiry_date': TextInput(attrs={"type":"date"}),
'available_hosts' : Select(),
'country' : Select(),
'feature' : Select(),
'activation_request' : HiddenInput(),
'hostid_provision' : HiddenInput(),
}
The KeyDefinition indeed exists: exactly the one you are editing matches. The clean() method will always run when validating a form. That is why you should exclude that object, so with:
class KeyDefinition(models.Model):
def clean(self):
# …
# ensure that each user can't have two keys with the same name
key_name_exists = (
KeyDefinition.objects.filter(
key_name=self.key_name, developer_email=self.developer_email
)
.exclude(pk=self.pk)
.exists()
)
if key_name_exists:
raise ValidationError({'key_name': ['This Key Name already exists']})
return super().clean()
We here thus exclude our own object with .exclude(pk=self.pk).
Note: It is more efficient to use .exists() [Django-doc]
than to check the truthiness of a QuerySet, since this will not load the records from the queryset, and thus will minimize the bandwidth used between the database
and the Django/Python layer.
I have looked through the similar SOverflow questions and no answer helped.
I am trying to create a follower/following relationship when a button is pressed, passing the info through Javascript...
function follow(user) {
fetch(`/profile/${user.id}`, {
method: 'POST',
body: JSON.stringify({
followed: user.id
})
})
.then(response => response.json())
.then(() => load_profile(user.id))
}
...then I receive it and try to save it or update it...
#csrf_exempt
#login_required
def view_profile(request, user_id):
if request.method == "POST":
data = json.loads(request.body)
followed = data.get("followed", "")
follower = request.user.id
obj, created = UserFollowing.objects.get_or_create(user_id=followed, following_user_id=follower, following=True)
if not created:
obj(following=False)
obj.save()
return JsonResponse({"message": "(Un)Followed successfully."}, status=201)
... and this is the model:
class User(AbstractUser):
pass
def serialize(self):
return {
"id": self.id,
"user": self.username,
"following": self.following.count(),
"followers": self.followers.count(),
}
class UserFollowing(models.Model):
user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name="following")
following_user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name="followers")
following = models.BooleanField(blank=False, default=True)
ERROR: ValueError: Cannot assign "3": "UserFollowing.user_id" must be a "User" instance.
I have tried to pass different values such as the username, the object itself, to no avail.
This line:
followed = data.get("followed", "")
If your body message (data) does not contain followed keyword than empty string will be assigned to followed variable otherwise a number.
Both cases would make you trouble later at this line, where followed has to be a user object and can't be an empty string or a number.
obj, created = UserFollowing.objects.get_or_create(user_id=followed, following_user_id=follower, following=True)
You will also get the same error at the following line where you assign a number to the follower variable which, according to your model, has to be a user object as well.
follower = request.user.id
Possible solution to that:
#csrf_exempt
#login_required
def view_profile(request, user_id):
if request.method == "POST":
data = json.loads(request.body)
if "followed" in data:
followed = data["followed"]
try:
user = User.objects.get(id=followed)
except:
# user does not exist
follower = request.user
obj, created = UserFollowing.objects.get_or_create(user_id=user, following_user_id=follower, following=True)
if not created:
obj(following=False)
obj.save()
return JsonResponse({"message": "(Un)Followed successfully."}, status=201)
I have been trying to make a Form for item removal but I don't know how to connect the field to the model, here's what I'm doing:
class StudentForm(forms.ModelForm):
queryset = Student.objects.filter().values('name')
choices = [('', var) for var in queryset]
names = forms.ChoiceField(choices=choices)
class Meta:
model = Student
fields = '__all__'
I used this class to connect to the Student model and use its fields, however I want to add a field of my own to it, which is names = forms.ChoiceField(choices=choices), but what I want to know is, how would I connect this field that lists all names, for example, to the form to make it so that I could pick an object's name and then I could change/delete it accordingly?
class StudentRegister(generic.FormView):
template_name = 'students/student_form.html'
form_class = StudentForm
success_url = '/'
def form_valid(self, form):
form.save(commit=True)
return super().form_valid(form)
This is my views.py and as you can see, it automatically sets the values of the form because those are already "tied in" to a model field, but not the choice field I added. How would I correct this?
1) Approach using queryset
class StudentForm(forms.ModelForm):
name = forms.ChoiceField(queryset=Student.objects.all())
or
name = forms.ChoiceField([("%s" % stud['id'], "%s" % stud['name']) for stud in
Student.objects.all().values('id', 'name')])
class Student(models.Model):
# existing fields comes here
def __str__(self):
return self.name
2) Dynamic load using ajax
class StudentForm(forms.ModelForm):
name = forms.ChoiceField()
class Student(models.Model):
# existing fields comes here
def __str__(self):
return self.name
Load data using ajax
<script>
(function($){
//get your data
function get_data(){
$("#<id_of_choice_field>").empty();
$.ajax ({
type: "POST",
url: "<url_for_getting_data>",
data: { "csrfmiddlewaretoken": "{{ csrf_token }}" },
cache: false,
success: function(json) {
if (json) {
for (var source in json) {
$("#id_of_choice_field").prepend("<option value='"+json[source].id+"'>"+json[source].name+"</option>");
}
}
}
});
$("#id_of_choice_field").prepend("<option value='' selected='selected'>--------------</option>");
}
get_data();
}(django.jQuery));
</script>
AJAX URL & method
url(r'^get-all-student/$', get_all_student)
def get_all_student(request):
"""
:param request:
:return:
"""
if request.method == "POST" and request.is_ajax():
all_student = []
for student in Student.objects.all().values('id', 'name'):
all_student.append({'id': student['id'], 'name': student['name']})
return HttpResponse(json.dumps(all_student), content_type="application/json")
return HttpResponseNotAllowed(['GET'])
You can pass all the names to a single field with:
class StudentForm(forms.ModelForm):
name = forms.ModelMultipleChoiceField(queryset=Student.objects.all())
class Meta:
model = Student
fields = '__all__'
and let your Student model return the name:
class Student(models.Model):
# Model fields
def __str__(self):
return self.name
You can look for more ways for your forms to interact with models at https://docs.djangoproject.com/en/2.1/topics/forms/modelforms/#overriding-the-default-fields
hey i tried this code but still have an error that is Not Null constraint failed i know this is because of my cheque_no that is unique type but how could i remove this error i did not remove my cheque_no is unique because its required.now am getting this problem i want to save these entries in my model.
views.py
#csrf_exempt
def jsdata(request):
table_data = json.loads(request.POST.get('MyData'))
# print(table_data)
r_data = {
'success': True,
}
for data in table_data:
# Since you are just creating objects you don't need to save created object in a variable.
Mvouchar.objects.create(bill_no = data['BillNo'], bill_details=data['BillDetails'],am=data['Amount'])
# r_data['success'] = False
# IMO Views responding to ajax requests should send JsonResponse
if r_data['success']:
r_data['msg'] = 'Data Saved'
else:
r_data['msg'] = 'Not all Data Saved'
return JsonResponse(r_data)
models.py
class Mvouchar(models.Model):
related = models.ForeignKey(Signs, on_delete=models.CASCADE, null=True, blank=True)
bill_no = models.CharField(max_length=8000, null=True, blank=True)
bill_details = models.CharField(max_length=10000, null=True, blank=True)
am = models.CharField(max_length=30000, null=True, blank=True)
cheque_no = models.PositiveIntegerField(validators=[MaxValueValidator(6)], unique=True, help_text='integers only')
def __str__(self):
if self.related:
return self.related.relation.username.title()
else:
return 'no related!'
class Meta:
verbose_name_plural = "Single Cheque Multiple Vouchar Of Users"
javascript
$("#btnjson").click(function () {
var array1 = [];
$("tbody tr").each(function () {
var firstTableData = {};
firstTableData.BillNo = $(this).find('td').eq(0).text();
firstTableData.BillDetails = $(this).find('td').eq(1).text();
firstTableData.Amount = $(this).find('td').eq(2).text();
array1.push(firstTableData);
//}
});
alert(JSON.stringify(array1));
$.ajax({
type: "POST",
url: "/jsondata/",
dataType: 'json',
data: {MyData: JSON.stringify(array1)},
success: function(msg){
alert(msg);
}
});
return false;
} );
});
from django.core.validators import RegexValidator
CHEQUE_REGEX = RegexValidator(
regex=r'^\d{6}$',
message="Cheque Number must be exactly 6 digits"
)
class Mvouchar(models.Model):
...
cheque_no = models.CharField(validators=[CHEQUE_REGEX, ], unique=True, max_length=6, help_text='integers only')
...
If you read the second traceback (The first traceback is from a different view views.mvoucha, and you didn't include that code in the question), you'll find that the problem is this line in your view function.
table_data = json.loads(request.POST.get('MyData'))
What's happening is that request.POST.get('MyData') returns None, which can't be represented as json. That's why the json encoder raises a TypeError.
The request.POST dictionary is used with form data. When you want to parse a json payload, you have to use request.body instead. For example like this:
table_data = json.loads(request.body).get('MyData')
https://docs.djangoproject.com/en/2.1/ref/request-response/#django.http.HttpRequest.POST
I'm trying to create a function that filters objects by a ForeingKey field (or rather the object being referenced in the field). I wrote the function which I have included below, which is based off the Tastypie Cookbook's Adding Search Functionality. The function does work in that it returns the objects, however it only returns the objects' names as a string.
This is the result of calling the function:
{'receipt_items': [<ReceiptItem: Item1>, <ReceiptItem: Item2>, <ReceiptItem: Item3>]}
Here is my resource:
class ReceiptItemResource(ModelResource):
receipt = fields.ToOneField(ReceiptResource, 'receipt', full = True)
class Meta:
queryset = ReceiptItem.objects.all()
serializer = Serializer()
allowed_methods = ['get', 'post', 'put', 'delete']
always_return_data = True
def prepend_urls(self):
return [
url(r'^(?P<resource_name>%s)/filter%s$' % (self._meta.resource_name, trailing_slash()), self.wrap_view('filter_by_receipt'), name = 'filter_by_receipt'),
]
def filter_by_receipt(self, request, **kwargs):
data = self.deserialize(request, request.body, format = request.META.get('CONTENT_TYPE', 'application/json'))
receipt_id = data.get('receipt_id', '')
print receipt_id
receipt = Receipt.objects.get(id = receipt_id)
receipt_items = ReceiptItem.objects.filter(receipt = receipt)
item_list = {
'receipt_items' : receipt_items,
}
print item_list
return self.create_response(request, receipt_items)
#return super(ReceiptItemResource, self).get_object_list(request).filter(receipt = receipt)
def get_object_list(self, request):
user = request.user
member = user.member
owner = member.owner
return super(ReceiptItemResource, self).get_object_list(request).filter(owner = owner)
Ideally I would like this function to return the full object details in JSON. Is there any way to make this happen?
I have looked into Tastypie's resource filtering, however I don't believe this would work since the field I am trying to filter by is a ForeignKey.
All help is greatly appreciated, thank you.
You should not need to do this. You can filter on foreignKey by default using the filtering option
from tastypie.resources import ALL, ALL_WITH_RELATIONS
class RecieptResource(ModelResource):
class Meta:
fields = ["id"]
filtering = {
"id": ALL
}
class ReceiptItemResource(ModelResource):
receipt = fields.ToOneField(ReceiptResource, 'receipt', full = True)
class Meta:
queryset = ReceiptItem.objects.all()
allowed_methods = ['get', 'post', 'put', 'delete']
always_return_data = True
filtering = {
"reciept": ALL_WITH_RELATIONS
}
Now the following should url (depending on how you have yours configured) should give you what you want
/api/v1/reciept-item/?receipt__id=1&format=json