Django forms - is there a way to dynamically edit a FilePathField? - django

I have a django form which I am using to access a directory. However I would like to access two different directories based on a given input, but have one FilePathField.
As an example - I have two panels - 'panel1' and 'panel2'. The directory I would like to access is the analysis directory of each of these panels as such:
/path/destination/panel1/analysis/
/path/destination/panel2/analysis/
In each of these analysis directories are directories starting with "Experiment" which I would like a user to be able to choose to obtain some results.
I have my form:
class RunUploadForm(forms.Form):
directory_path = forms.FilePathField(
path='/path/destination/panel1/analysis',
required=True,
allow_folders=True,
allow_files=False,
recursive=True,
match="Experiment*",
label="Pick folder for results:"
)
class Meta:
fields = ('directory_path',)
This only allows the user to access panel1 directory as it is hardcoded into the path arg. Is there a way to dynamically change this path argument, maybe with a choicefield?

One way of doing so would be to pass the path as an argument to the __init__ method of the form, for example:
class RunUploadForm(forms.Form):
directory_path = forms.FilePathField(
path='/path/destination/panel1/analysis',
required=True,
allow_folders=True,
allow_files=False,
recursive=True,
match="Experiment*",
label="Pick folder for results:"
)
class Meta:
fields = ('directory_path',)
def __init__(self, *args, **kwargs):
path = kwargs.pop('path', 'somedefaultvalue')
super().__init__(*args, **kwargs)
self.fields['directory_path'] = forms.FilePathField(
path=path,
required=True,
allow_folders=True,
allow_files=False,
recursive=True,
match="Experiment*",
label="Pick folder for results:"
)
You need to crate a new instance of FilePathField because choices for this kind of field are generated on __init__

Related

How do I accept multiple choices for a Charfield from CheckboxSelectMultiple widget in Django?

I'm trying to build a form in Django 1.11, where I have a set of checkboxes on a watch list to allow a user to set multiple alerts on an item they want to receive notifications on later. However, I'm not sure how to represent multiple options on a field like this.
Here's my model code:
class Watchlist(models.Model):
CLOSES_IN_2_WEEKS, CLOSES_IN_1_WEEKS, CLOSES_IN_3_DAYS, CLOSES_TOMORROW = (
"CLOSES_IN_2_WEEKS",
"CLOSES_IN_1_WEEKS",
"CLOSES_IN_3_DAYS",
"CLOSES_TOMORROW"
)
ALERT_OPTIONS = (
(CLOSES_IN_2_WEEKS, "Closes in 2 weeks",),
(CLOSES_IN_1_WEEKS, "Closes in 1 weeks",),
(CLOSES_IN_3_DAYS, "Closes in 3 days",),
(CLOSES_TOMORROW, "Closes tomorrow",),
)
# I want to store more than one option
alert_options = models.CharField(max_length=255, blank=True)
def save(self):
"""
If this is submitted create 1 alert:
"CLOSES_IN_1_WEEKS"
If this submitted, create 3 alerts:
"CLOSES_IN_2_WEEKS",
"CLOSES_IN_1_WEEKS",
"CLOSES_IN_3_DAYS",
"""
# split the submitted text values, to create them
# yes, this should probably be a formset. I wasn't sure how I'd
# handle the logic of showing 4 optional alerts, and only creating models
# on the form submission, and remembering to delete them when a user
# unchecked the choices in the form below
And here's the form I'm using, below. I'm hooking into the __init__ method to pre-populate the form with possible choices.
class WatchlistForm(forms.ModelForm):
alert_options = forms.ChoiceField(
choices=[],
label="Alert Options",
required=False,
widget=forms.CheckboxSelectMultiple(),
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["alert_options"].choices = WatchlistForm.ALERT_OPTIONS
def clean_alert_options(self):
# I'm dropping into ipython to look around here
import ipdb; ipdb.set_trace()
return data["alert_options"]
class Meta:
model = WatchlistForm
fields = ("alert_options")
This currently works fine for a single checked checkbox, but once I have more than one, only the last choice is selected, and I can't quite figure out how to access it.
How can I capture all the choices here, rather than just the one?
I'm aware I probably should be using a formset here. The thing is it wasn't obvious to me how to create a set of pre-populated formset options to represent some active and some inactive alert choices.
Using a test to show what I'm going for
If it helps, I'm trying to save the information so it would be stored like so - I've added some pseudo code based on me using pytest in a test suite.
def test_form_accepts_multiple_alert_values(self, db, verified_user):
form_data = {
"user_id": verified_user.id,
"alert_options": "CLOSES_IN_2_WEEKS CLOSES_IN_3_DAYS",
}
submission = forms.WatchlistForm(form_data)
instance = submission.save()
assert instance.alert_options == "CLOSES_IN_2_WEEKS CLOSES_IN_3_DAYS",

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.

Loading choices into choicefield - strange behavior

I have a django form with a choicefield, where I dynamically load some choices into the field:
class EntryForm(forms.Form):
project = forms.ChoiceField()
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(EntryForm, self).__init__( *args, **kwargs)
CHOICES2=[]
for x in Project.objects.all() :
if user in x.users.all():
CHOICES2.append((x.name,x.name))
CHOICES1 = [(x.name,x.name) for x in Project.objects.all()]
print CHOICES2==CHOICES1 #this is True in this case
self.fields['project']=forms.ChoiceField(choices=CHOICES2)
The form is loaded into the template with {{form.as_table}}. The form does not show a dropdown for the project field.
Now the strange thing: if I change the last line to:
self.fields['project']=forms.ChoiceField(choices=CHOICES1)
it works, although the print statement of the "=="" comparison returns True (the lists are purposely the same - this is just for testing). I really have no idea how this can even work technically.
Edit - my project model:
class Project(BaseModel):
name = models.CharField(max_length=80)
users = models.ManyToManyField(User)
Your field named project already exists and there's no need to construct another one as you are doing. It's better to just set the choices on the existing field:
self.fields['project'].choices = CHOICES2
But maybe you'd be better off using a ModelChoiceField:
project = ModelChoiceField(queryset=Project.objects.none())
and then set the queryset you want in init like so:
self.fields['project'].queryset=Project.objects.filter(users__in=[user])
..which should give you a list of all projects associated with user.
I think you have to use queryset argument, which is mandatory:
https://docs.djangoproject.com/en/1.8/ref/forms/fields/#django.forms.ModelChoiceField.queryset
ChoiceField must be declared with (queryset=None), and in the __init__ method you complete the query:
https://docs.djangoproject.com/en/1.11/ref/forms/fields/#fields-which-handle-relationships
The problem could be about the execution order of the queries, or the cache of non-lazy queries.
And I agree with little_birdie: the field already exists.

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.

Can't save a django form with this autocomplete

I'm using autocomplete-light and for some reason this specific class is not working--I can't see any major differences between it and the working autocompletes. My VirtualHost contains a fk to a Host provided that Host.contain_virtuals=True
Here's my form:
class VirtualHostForm(ServerForm):
def __init__(self, *args, **kwargs):
super(VirtualHostForm, self).__init__(*args, **kwargs)
self.helper.form_id = 'virtual_host_form'
host = forms.ModelChoiceField(Host.objects.all(),
widget=autocomplete_light.ChoiceWidget('HostAutocomplete'),
label='Associated Host'
class Meta:
model = Virtual
fields = ServerForm.Meta.fields + ['host',]
widgets = autocomplete_light.get_widgets_dict(Server)
I've tried two ways, each with their own errors:
class HostAutocomplete(autocomplete_light.AutocompleteBase):
#registers autocomplete for hosts that can contain virtuals
autocomplete_js_attributes = {'placeholder': 'Select a host'}
widget_template='assets/subtemplates/autocomplete_remove.html',
choice_template='assets/_autocomplete_choice.html',
def choices_for_request(self):
q = self.request.GET.get('q', '')
hosts = Host.objects.values_list('name', flat=True)
return hosts.filter(name__icontains=q, contain_virtuals=True).distinct()
autocomplete_light.register(HostAutocomplete)
This way, I get the error: 'NotImplementedType' object is not callable. That seemed to relate to not having a choices_for_values method (although some of my other Autocompletes don't) so I added:
def choices_for_values(self):
choices = Host.objects.filter(id__in=self.values)
return choices
(I don't really know what I'm doing here--I couldn't find much in the documentation, so I took my best guess).
That gave me a invalid literal for int() with base 10: which I guess means it's looking at the name, instead of the pk for a foreign key relationship? That's a guess.
It should be noted that all of the above attempts did not render the template-formatting correctly, but did at least give the correct options for the choices.
So finally I tried:
autocomplete_light.register(
Host,
autocomplete_light.AutocompleteModelTemplate,
name='HostAutocomplete',
widget_template='assets/subtemplates/autocomplete_remove.html',
choice_template='assets/_autocomplete_choice.html',
autocomplete_js_attributes={'placeholder': 'Type associated host'},
search_fields=['name'],
)
which saves (and contains the correct formatting) but does not filter the choices based on contain_virtuals=True; it just includes all possible hosts.
EDIT:
Thanks to #jpic's help below, this works:
class HostAutocomplete(autocomplete_light.AutocompleteModelTemplate):
#registers autocomplete for hosts that can contain virtuals
autocomplete_js_attributes = {'placeholder': 'Select a host'}
choice_template='assets/_autocomplete_choice.html',
def choices_for_request(self):
q = self.request.GET.get('q', '')
hosts = Host.objects.filter(contain_virtuals=True,name__icontains=q).distinct()
return hosts
def choices_for_values(self):
choices = Host.objects.filter(id__in=self.values)
return choices
autocomplete_light.register(Host, HostAutocomplete)
This is because you inherit from AutocompleteBase instead of AutocompleteModelBase ! You could use AutocompleteModelTemplate as well.
Check out how Autocomplete design is explained in docs for v2 (that part doesn't change from v1 to v2): http://django-autocomplete-light.readthedocs.org/en/v2/autocomplete.html