Django - How to customize a checkbox in a form? - django

I'm out of clues on how to customize a form with checkboxes. Basically I need to choose in the queryset only two choices of a list, I've tried to use "filter(pk=2, pk=3)" but it is not working. I've also to make the "pk=2" choice checked and not editable and the "pk=3" editable and not checked by default.
My form(does not work, filter(pk=2, pk=3) is not valid):
class PrimaryCategoryForm(forms.ModelForm):
primarycategorytype = forms.ModelMultipleChoiceField(queryset=PrimaryAdCategoryType.objects.filter(pk=2, pk=3), required=True, widget=forms.CheckboxSelectMultiple)
Any clues on how to achieve this?
Best Regards,

Trying building your queryset using the pythons union operator -
class PrimaryCategoryForm(forms.ModelForm):
set_1 = PrimaryAdCategoryType.objects.filter(pk=2)
set_2 = PrimaryAdCategoryType.objects.filter(pk=3)
qs = set_1 | set_2
primarycategorytype = forms.ModelMultipleChoiceField(queryset=qs,
required=True,
widget=forms.CheckboxSelectMultiple)
The | operator is creating a new set with all items from set_1 and all items from set_2.
UPDATE
Whilst the method above will support creating complicated sets. Martin's suggestion in the comments is probably sufficient if you simply want to pick a few pk's -
class PrimaryCategoryForm(forms.ModelForm):
qs=PrimaryAdCategoryType.objects.filter(pk__in=[2,3])
primarycategorytype = forms.ModelMultipleChoiceField(queryset=qs,
required=True,
widget=forms.CheckboxSelectMultiple)
EDITABLE CHECKBOXES
I think when it comes to making the checkboxes editable and preselected you may need to resort to javascript. Perhaps add data-attributes to the widget, then create a jquery function that manipulates the checkboxes on the basis of the data-attributes. Something like -
attrs = {'data-selected-value': 2, 'data-readonly-value': 2}
primarycateogrytype = forms.ModelMultipleChoiceField(attrs=attrs)
Then in the javascript -
var readonlyCheckboxValue = $('#your_mulliplecheckboxselect).data('selected-value');
var $readonlyCheckbox = $('#your_multiplecheckboxselect .input[value=readonlyCheckboxValue]');
$readonlyCheckbox.attr('readonly', true);
I haven't tested this code, but it should give you an idea of what I'm suggesting. Do check out the data-attribute if you haven't come across it already - it's pretty awesome.

Its possible that you would need the form to be dynamic in nature. You would need to override the __init__ method for the class.
Something like this here: How to Modify Choices of ModelMultipleChoiceField
An example:
class ServiceStartForm(forms.Form):
serviceList = service.models.Service.objects.all()
print serviceList.count()
b = {}
for aService in serviceList:
b[aService.id] = aService.name
c = b.items()
print "Within the form, ", serviceList.count()
serviceChoice = forms.ChoiceField(choices=c, widget=forms.Select())
input_directory = forms.CharField(max_length=200)
output_directory = forms.CharField(max_length=200)
def __init__(self, *args, **kwargs):
super(ServiceStartForm, self).__init__(*args, **kwargs)
serviceList = service.models.Service.objects.all()
b = {}
for aService in serviceList:
b[aService.id] = aService.name
c = b.items()
self.fields["serviceChoice"].choices = c

Related

Django - Custom values for inline list item field

After searching for a while, I can't seem to find any answer for this so I'm asking this question. What I want to is display some values based on a person's salary and a bracket. My models looks like this.
class Employee
salary = models.DecimalField
class Benefit
some_fields
class EmployeeBenefit
employee = models.ForeignKey
benefit = models.ForeignKey
class BenefitVersion
benefit = models.ForeignKey
class BenefitBracket
benefit_version = models.ForeignKey
from_salary = models.DecimalField
to_salary = models.DecimalField
the_fields_I_want_to_display
As you can see it's quite deep, there would be a bunch of querying to do to get to the fields I want.
I basically need to get the employee's salary(which is easy because this is gonna be inside EmployeeAdmin) then get the current benefit of the EmployeeBenefit list item, then based on the benefit and the employee's salary, get the bracket and then display some of it's fields on the inline.
I want to display the the_fields_I_want_to_display on the admin.TabularInline for EmployeeBenefit inside my EmployeeAdmin. I was testing using a forms.ModelForm in the inline and modifying it's contents using get_form based on this answer but django is not calling get_form. I also previously tried using calculated_fields but it's not being rendered as well.
I'm using django 3.1.7 btw
Edit:
I found this and try it out right now. I think it has potential but unfortunately the obj that get_formset gives is Employee so I still need to find a way to get the right EmployeeBenefit for the list item
After some more searching, I already figured out a way to do this. Based on this SO answer, I got an idea on how I should receive the data I passed to the formset using get_formset.
So, in my get_formset, I did something like this.
def get_formset(self, request, obj=None, **kwargs):
formset = super(Inline, self).get_formset(request, obj, **kwargs)
formset.request = request
benefit_details_dict_arr = []
emp_contribs = models.EmployeeBenefit.objects.filter(employee=obj)
#because what I'm getting here is Employee and not a EmployeeBenefit, I have to query it first
for contrib in emp_contribs:
bracket = contrib.get_benefit_bracket(datetime.now())
benefit_details_dict_arr.append({
"key": bracket.val,
"key2": bracket.val2,
})
formset.benefit_details = benefit_details_dict_arr
return formset
Then on the formset, I receive the data this way.
class EmpBenefitFormSet(forms.models.BaseInlineFormSet):
model = models.EmployeeBenefit
def __init__(self, *args, **kwargs):
super(EmpBenefitFormSet, self).__init__(*args, **kwargs)
cntr = 0
for form in self.forms:
init_data = self.benefit_details[cntr]
for field in form.fields:
if field in init_data:
form.initial[field] = init_data[field]
form.fields[field].required = False
form.fields[field].disabled = True
cntr += 1
Also, if you're gonna use this way of receiving the data, each field you want to inflate should be declared on the fields of the inline. This should work if the fields you want to set an initial value are actual fields of the model. You might need to tweak this a bit to work for custom fields

Django forms: Is it possible to have multiple drop down menus for different tags within a field?

I have a form in a formset where I would like to display multiple drop down menus under a single field 'tests'. I have achieved this in the form of having a single dropdown menu within 'optgroup' tags (see image below).
I guess this way you can only choose a single value.
However, is it possible to 'nest' these drop downs? I.e have them all under one field 'tests', but be able to have several dropdowns with 'tags' and choose results for each tag? Or do I need a field for each 'tag'?
My forms.py:
class ReportForm(forms.ModelForm):
summary = forms.CharField(
widget=forms.Textarea(attrs={'rows':3, 'cols':70}),
label='',
required=False)
tests = forms.CharField(widget=forms.HiddenInput())
class Meta:
model = ClinicallyReportedSample
fields = ('id', 'summary', 'tests', 'hilis_reported')
def __init__(self, *args, **kwargs):
json_data = kwargs.pop('json_data', None)
super(ReportForm, self).__init__(*args, **kwargs)
crs_obj = self.instance
for j in json_data:
if j['lab_no'] == str(crs_obj):
json = j
summary = json['summary']
self.fields['summary'].initial = summary
self.fields['reported'].label = crs_obj
tests = json.get('tests', None)
if tests:
test_choices = (
('mutated', 'mutated'),
('mutated - see comments', 'mutated - see comments'),
('awaiting confirmation', 'awaiting confirmation'),
)
self.fields['tests'] = forms.ChoiceField(
required=True,
label='Current or repeat samples?',
choices=((k, test_choices) for k in tests),
)
What I get now:
I would instead want a dropdown for each gene, and those choices. Do I need to make a field for each gene? The problem I have with doing this is that each result can have 0-10 genes, and this would be incredibly difficult to render in a HTML table.
Thanks
You probably want to implement something template/client-side to handle that, such as Chosen or Selectize.js (see the option groups examples).
Then on your form class implement a clean and/or clean_[field_name] method if you need to get your selected data in the format you want.

Queryset for django-select2 widget being ignored

In my project I am using Django-Select2 heavily, particularly its ModelSelect2Widget as my users frequently need to select from lists of 2,000-6,000 items. In all my uses of it up 'til now, the queryset for the widget has always been called as ".all()" instances of a model, for the user to select from, and there's been no issues.
Now, however, I have implementations in different parts of the project for which filtering the queryset of options for the widget is necessary. In all of these cases, however, any modification of the queryset seems to have no effect, and I'm wondering if there is a problem with the widget itself.
In the primary case, the items in the database are boolean-flagged as active/inactive (about 65% inactive), and I need to only have active items available for the end-user to select.
I'm able to filter the queries correctly via the shell.
In the form definitiion, any filtering (".filter(flag_active=True)", or even setting the queryset to ".none()" has no effect – there is no apparent change in the options in the dropdown/autocomplete. Being that it is a select2 input, I can only view a small number of items at a time, but both the initial retrieved population and the winnowed-down selection as I type indicate that the filters are not been followed.
MODEL:
class Inventory_product_base(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid4,null=False)
upc = models.CharField(max_length=96,null=True,blank=True)
name = models.CharField('Item name',max_length=96,null=False)
flag_active = models.BooleanField("Active item",default=True)
price = models.DecimalField(max_digits=8,decimal_places=3,null=True,blank=True)
unit_of_measure = models.CharField('UOM',max_length=24, choices=UNITS_OF_MEASURE,default='EACH')
spec = models.CharField(max_length=36,null=True,blank=True)
category = models.ForeignKey(Inventory_category,on_delete=models.CASCADE,related_name='cat_products')
subcategory = models.ForeignKey(Inventory_subcategory,on_delete=models.CASCADE,related_name='subcat_products')
note = models.CharField(max_length=275,null=True,blank=True)
def __str__(self):
return str(self.name)
FORM:
class InventoryCatalogUpdateProductsForm(forms.ModelForm):
parent_product_base = forms.ModelChoiceField(
queryset=Inventory_product_base.objects.filter(flag_active=True),
label=u"",
widget=ModelSelect2Widget(
model=Inventory_product_base,
search_fields=['name__icontains'],
attrs={'data-placeholder': 'Select product...', 'data-width': '100%'},),)
class Meta():
model = Inventory_unit_catalog
fields = ('parent_product_base',)
class InventoryCatalogUpdateAllProductsForm(forms.ModelForm):
parent_product_base = forms.ModelChoiceField(
queryset=Inventory_product_base.objects.all(),
label=u"",
widget=ModelSelect2Widget(
model=Inventory_product_base,
search_fields=['name__icontains'],
attrs={'data-placeholder': 'Select product...', 'data-width': '100%'},),)
class Meta():
model = Inventory_unit_catalog
fields = ('parent_product_base',)
InventoryCatalogUpdateProductsFormset = modelformset_factory(model=Inventory_unit_catalog,form=InventoryCatalogUpdateProductsForm,extra=10,can_delete=True)
InventoryCatalogUpdateAllProductsFormset = modelformset_factory(model=Inventory_unit_catalog,form=InventoryCatalogUpdateAllProductsForm,extra=10,can_delete=True)
VIEW:
if product_flag == 'active':
formset = InventoryCatalogUpdateProductsFormset(queryset=parent_unit_catalog.products.filter(flag_active=True))
else:
formset = InventoryCatalogUpdateAllProductsFormset(queryset=parent_unit_catalog.products.all())
As noted, if I change the above queryset to .none() (or anything else, either in the widget or in the view) there is no difference in the rendered choices in the select2 field.
I've tried separate, parallel forms and formsets. Originally I tried for a more sophisticated approach, to pass a parameter and have the different querysets selected within a single form, by adding the following:
def __init__(self, *args, **kwargs):
self.product_flag = kwargs.pop('product_flag')
super(InventoryCatalogAddToForm, self).__init__(*args, **kwargs)
print("__init__ has product_flag: ",self.product_flag)
if self.product_flag == 'active':
self.fields['parent_product_base'].queryset = Inventory_product_base.objects.filter(flag_active=True)
print("Screened for flag_active=True")
else:
self.fields['parent_product_base'].queryset = Inventory_product_base.objects.all()
print("Screened for flag_active=False")
and I was able to verify by the debug prints that the correct filter choices were executing, but without any effect. So I moved back to a simpler, more direct approach of separate forms, and still nothing.
Any advice would be welcome. My project is several months in and Django-Select2 is one of the foundations across it, I would hate to learn that it cannot filter the select2 input and I would need to find a replacement.
self.fields['parent_product_base'].queryset sets the queryset for the formfield (i.e. allowed choices for validation).
Use self.fields['parent_product_base'].widget.queryset to set the widget's choices.

autocomplete_light is not showing correct suggestions with choice_for_request

I have autocomplete-light in the django modal form. I wanted to apply dynamic filtering in the suggestion box, that's why I have used choice_for_request() in the autocompletebasemodel. But because of using choice_for_request(), the suggestions are not according to the keyword typed but all the values that can be entered.
This is my form:
class CamenuForm(autocomplete_light.ModelForm):
class Meta:
model = Ca_dispensaries_item
exclude = ('dispensary',)
autocomplete_fields = ('item',)
def __init__(self, *args, **kwargs):
self.category = kwargs.pop('category', None)
super(CamenuForm, self).__init__(*args, **kwargs)
self.fields['item'].queryset=Items.objects.filter(product_type__name=self.category)
This is the registry and the class:
autocomplete_light.register(Items, AutoComplete )
class:
class AutoComplete(autocomplete_light.AutocompleteModelBase):
search_fields=('item_name',)
def choices_for_request(self):
category = self.request.session.get('category','')
if category:
choices = Items.objects.filter(product_type__name=category)
return self.order_choices(choices)[0:self.limit_choices]
I really dont know what changes to make in changes_for_request so as to make it work correctly
After going through various documents, the solution which worked as correctly as it should be is
def choices_for_request(self):
category = self.request.session.get('category','')
item=self.request.GET.get('q','')
choices = self.choices.all()
if item:
choices = choices.filter(item_name__icontains=item)
super(AutoComplete, self).choices_for_request()
if category:
choices = choices.filter(product_type__name=category)
return self.order_choices(choices)[0:self.limit_choices]
I missed out
item=self.request.GET.get('q','')
autocomplete-light uses the get method and the predefined literal q to transfer the value typed by user.
I wasn't able to crack out the meaning of q. After some hit and trial, I got that it is used to store the user given value in suggestion box.

update django choice field with database results

I am developing an application using django where the UI needs to be updated when user interacts with it. For instance I have a Drop down field where the user selects a drink and submits it then based on that a dropdown with the places that drink is available, price and quantity at each place needs to be displayed. The user will then further submit the form for second process.
From my understanding the Forms in django are pre-defined and I am not able to think of a way using which I could achieve this.
What I could come up was defining a regular form class
class dform(forms.Form):
SOURCES_CHOICES = (
(A, 'A'),
(E, 'E'),
)
drink = forms.ChoiceField(choices = SOURCES_CHOICES)
location = forms.ChoiceField(choices = **GET THIS FROM DATABASE**)
quantity = forms.ChoiceField(choices = **GET THIS FROM DATABASE**)
.
.
.
My view is like,
def getdrink():
if request.method == 'POST':
#code for handling form
drink = dform.cleaned_data['drink']
#code to get values from database
I have no idea how to generate or populate or append the values i get from the database to the choicefield in my form. I did try looking up on SO but none of the solutions here explained properly how to do it. Also, due to certain requirements I am not using the models. So my database is not at all related to the models.
I am at a total loss Please help me out
class MyForm(forms.Form):
my_choice_field = forms.ChoiceField(choices=MY_CHOICES)
So if you want the values to be dynamic(or dependent of some logic) you can simply modify your code to something like this:
either
def get_my_choices():
# you place some logic here
return choices_list
class MyForm(forms.Form):
my_choice_field = forms.ChoiceField(choices=get_my_choices())
or
User_list = [ #place logic here]
class MyForm(forms.Form):
my_choice_field = forms.ChoiceField(choices=get_my_choices())
but once database value is updated, new data value will be popoulated only on restart of server.
So write a function like this in forms:
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['my_choice_field'] = forms.ChoiceField( choices=get_my_choices() )
or in place of the get_my_choices u can ad the USER_LIST too.
If you have models for location and quantity, a ModelChoiceField should work:
class dform(forms.Form):
location = forms.ModelChoiceField(queryset = Location.objects.all())
Otherwise, you'll need to query the database directly, for example:
class dform(forms.Form):
location = forms.ChoiceField(choices = get_location_choices())
# elsewhere
from django.db import connection
def get_location_choices():
cursor = connection.cursor()
cursor.execute("select location_id, name from location_table")
return cursor.fetchall()
The SQL query to use here depends on your database engine and table schema.
I think that, based on my understanding of your question, the best solution would be to include JSON objects with your form and load these using jQuery instead of submitting the form over and over. Included in your form, you should add something like:
class MyForm(forms.Form):
CHOICE_DICT = {
'choice_1': [
'option_1',
'option_2',
],
etc...
Then you should include form.CHOICE_DICT in your context, load that with jQuery, and render it depending on changes to other fields.