At some point I need to display a "disabled" (greyed out by disabled="disabled" attribute) input of type "select". As specified in the standard (xhtml and html4), inputs of type "select" can not have the "readonly" attribute. Note that this is for presentation purposes only, the actual value must end up in the POST. So here is what I do (quoting a part of the form declaration in django):
from django import forms
_choices = ['to be', 'not to be']
class SomeForm(forms.Form):
field = forms.ChoiceField(choices=[(item, item) for item in _choices],
widget=forms.HiddenInput()) # the real field
mock_field = forms.ChoiceField(required=False, # doesn't get submitted
choices=[(item, item) for item in _choices],
label="The question",
widget=forms.Select(attrs={'disabled':'disabled'}))
Then it is initialized like this:
initial_val = 'to be'
form = SomeForm(ititial={'field':initial_val,
'mock_field':initial_val})
And all is well. Well, until the form gets validated and one of the other fields fails the validation. When this happens, the form is reloaded and the values are preserved, but not the one of the "mock_field" - it never got submitted (it is disabled). So it is not preserved. While this doesn't affect the data integrity, it is still not so good presentation-wise.
Is there any way to preserve that field, with as little hackery as possible? The form is a part of a django.contrib.formtools.FormWizard and the initial values (and some fields) are generated dynamically. Basically, there is a lot of stuff going on already, it'd be great if it was possible not to overcomplicate things.
Browsers don't POST disabled fields.
You can try to copy fields initial value to mock_field in your Form's __init__
def __init__(self, *args, **kwargs):
super(SomeForm, self).__init__(*args, **kwargs)
mock_initial = self.fields['field'].initial
self.fields['mock_field'].initial = mock_initial
Code is not tested. Normally you would be concerned about form.data as well, but in this case it won't be different than initial
Well, this will be the first time I answer my question, but I've found a solution and (while it cerainly is a hack) it works.
Instead of getting the initial value from the form instance, - self.fields['whatever'].initial seems to be None inside the constructor, I am getting the value from keyword argument "initial". And then I set it as the only choice for the "mock" field. Like this:
from django import forms
_choices = ['to be', 'not to be']
class SomeForm(forms.Form):
field = forms.ChoiceField(choices=[(item, item) for item in _choices],
widget=forms.HiddenInput()) # the real field
mock_field = forms.ChoiceField(required=False, # doesn't get submitted
choices=[(item, item) for item in _choices],
label="The question",
widget=forms.Select(attrs={'disabled':'disabled'}))
def __init__(self, *args, **kwargs):
super(SomeForm, self).__init__(*args, **kwargs)
mock_initial = kwargs['initial']['field']
self.fields['mock_field'].choices = [(mock_initial, mock_initial),]
This probably needs some error handling. Obviously, this will not work if the initial value is not provided for the actual field.
Related
I was trying to implement Django-select2 for the first time.... I referred their documentation and some of the stack overflow solutions to implement it.... I managed to get ajax functionality work properly, also i am able to select multiple choices... however when I submit and validate the form, I am getting error like -> "Select a valid choice. 123456 is not one of the available choices."
I am not understanding what I am doing wrong....
here is my form.
class MyCustReqForm(forms.ModelForm):
initial_customer = forms.MultipleChoiceField(
widget=HeavySelect2MultipleWidget(data_view='customer_ajax',
attrs={'data-minimum-input-length': 4, 'delay':200},
model=Customer),
)
end_customer = forms.MultipleChoiceField(
widget=HeavySelect2MultipleWidget(data_view='customer_ajax',
attrs={'data-minimum-input-length': 4, 'delay':200},
model=Customer),
)
class Meta:
model = Workflow_Customer
fields = [ 'initial_customer', 'end_customer' ]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['initial_customer'].widget.attrs.update({'style': 'width:100%', 'data-placeholder': 'Select Customer'})
self.fields['end_customer'].widget.attrs.update({'style':'width:100%', 'data-placeholder':'Select end customer'})
and customer_ajax view calls below function...
def customer_select2(request):
term = request.GET.get("term", None)
if term:
res = list(Customer.objects.filter(Q(customer_number__contains=term) | Q(customer_name__contains=term)).values('id', 'customer_number', 'customer_name'))[:10]
if res:
result = [{'id': value['id'], 'text': value['customer_number'] + ' ' + value['customer_name'] } for index, value in enumerate(res)]
return JsonResponse({'err': 'nil', 'results': result}, safe=False)
return JsonResponse(data={'success': False, 'errors': 'No mathing items found'})
when I checked in debug mode. I found that choices are empty...
I appreciate for the quick help... if possible, please provide one complete example which explains how form defined and view used for Ajax function...
That happens because when you send your form there aren't initial choices so the chosen option don't pass the validation. When you send your form, the class MyCustReqForm is initialized and in that moment you must do a requets to get choices of your form field. That choices must containt your chosen option. For example:
self.fields['customer'].choices = call_to_end_point()
When the request is 'GET' there isn't problem, but in the request 'POST' is necessary to have some basic options.
PD: Sorry for my english
Solution is to write custom clean methods so this way you are defining your own validations for a field thus overriding django form validation for the choice field.
def clean_initial_customer(self):
choices = self.cleaned_data["initial_customer"]
# write your custom logics and remove any errors
let me know if you find some issue in implementing the same.
I have code snippets below, but the summary of my issue is this:
When displaying a formset with a known number of extra forms, each needing initialization with data from another object, the form's __init__() function is called for each form in the formset, and then one extra time. This causes an error, because the in the last call to __init__(), kwargs does not contain the expected item used for initialization.
My friends and I play a spreadsheet-based sports picking game which is very tedious to make changes to. I've wanted to learn Django for a while so I've been working on creating it as a webapp. Here's the relevant model for my issue:
class Pick(models.Model):
sheet = models.ForeignKey(Sheet)
game = models.ForeignKey(Game)
HOME = 'H'
AWAY = 'A'
PICK_TEAM_CHOICES = (
(HOME, 'Home'),
(AWAY, 'Away'),
)
pick_team = models.CharField(max_length=4,
choices=PICK_TEAM_CHOICES,
default=HOME)
... other stuff
And I've defined a form related to this model. The custom __init__() is so the form is initialized with information from a related Game object, passed with the 'initial' parameter in form creation:
class PickForm(ModelForm):
class Meta:
model = Pick
widgets = {'game': forms.HiddenInput()}
fields = ['sheet','game','amount','pick_type','pick_team']
def __init__(self, *args, **kwargs):
game = kwargs['initial']['game']
super(PickForm, self).__init__(*args, **kwargs)
self.fields['pick_team'].choices = ( ('H', game.home_team), ('A', game.away_team), )
I recently created the 'atomic' case where a user can pick a game via a PickForm in the related template, and that form is processed in the post() method of an adapted class based view. I'm trying to extend this case to handle multiple forms by creating a formset of PickForms:
class GameList(ListView):
template_name = 'app/games.html'
context_object_name = 'game_list'
def get_queryset(self):
games = get_list_or_404(Game, week = self.kwargs['week'])
return games
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
#create a formset of PickForms
PickFormSet = formset_factory(PickForm, extra = len(context['game_list'])-1)
pick_list = []
sheet = Sheet.objects.get(user=self.request.user,league_week=self.kwargs['week'])
picks = Pick.objects.filter(sheet = sheet)
for index, game in enumerate(context['game_list'],start=0):
#logic to create a list of objects for initial data
#create the formset with the pick dictionary
context['pickforms'] = PickFormSet(initial=[{'game':pick.game,
'sheet':pick.sheet,
'amount':pick.amount,
'pick_type':pick.pick_type,
'pick_team':pick.pick_team,} for pick in pick_list])
return context
The get_context_data() in the view contructs the pick_list properly and initializes the PickFormSet- my issue occurs in the template. I'm letting Django handle the rendering so it's very simple right now:
<form action="{% url 'game_list' week %}" method="post">
{{ pickforms }}
<input type="submit" name="pickformset" value="Submit" />
</form>
It seems Django actually initializes the PickForms while rendering the template, because the problem occurs in the __init__() of my PickForm. When debugging, i can step through as it initializes a PickForm for each form in the formset- there are a total of 6 right now. So for 'form-0' (autogenerated prefix, I think) through 'form-5', the initialization works properly, because the kwargs dictionary contains an 'initial', as expected.
However, after initializing those 6 forms, it loops through the __init__() again, for a form with prefix 'form-6' (so a 7th form). This form has no initial data associated with it, and therefore errors out in the __init__() due to a KeyError.
Why is Django attempting to create another form? I have extra = 5 specified in the formset_factory call, so there should only be 6 forms total, each having a related initial data dictionary.
I thought it may be related to the included management_form of the formset, however explicitly rendering that, then using a for loop to iterate over the PickForms didn't work either- I ran into the same issue where the template engine is trying to initialize an extra form without any initial data.
Also: I tried using modelformset_factory and specifying PickForm, however in that case the PickForms seem to be initialized differently. There's no 'initial' data in kwargs, but an 'instance' instead, which behaves differently. I'm still new to Django so I'm confused as to why the two methods would pass different kwargs to the PickForm __init__()
Alright, after mulling this over all day, I decided to add a try-except block in my __init__() to just catch the KeyError thrown when kwargs doesn't have initial data.
def __init__(self, *args, **kwargs):
try:
game = kwargs['initial']['game']
super(PickForm, self).__init__(*args, **kwargs)
self.fields['pick_team'].choices = ( ('H', game.home_team),('A', game.away_team), )
except KeyError:
super(PickForm, self).__init__(*args, **kwargs)
Adding that enabled rendering of the formset, and I realized something that (to me) isn't obvious from the docs:
The number of extra forms specified in the formset are created in addition to any forms defined by initial data. My interpretation of the docs was that I'd need enough extra forms to cover however many forms I wanted to initialize with desired data. In hindsight, that sort of bookeeping could be annoying- it's nice that your initial list of dictionaries can vary in length without having to worry about specifying the correct extra.
So, the formset initializes a form for every dictionary in initial list of dictionaries, then creates extra number of blank forms.
I feel rather dumb, but at the same time I don't think this specific case is all that clear in the documentation.
EDIT: On closer reading, there is some text that clearly says extra forms are created in addition to the number of forms generated by initial data. Conclusion: I need to learn to read
I am using Flask and WTforms in App Engine, trying to implement uniqueness contraint on one of the field. The question is big, please be patient and I have been stuck here from many hours, need some help from you people. Started learning App Engine, Flask and WTForms a month ago. Thanks in advance.
Application has model 'Team' as shown below:
class Team(db.Model):
name = db.StringProperty(required=True)
-- some other fields here --
Requirement: Name of the team has to be unique.
I have followed the links
http://www.codigomanso.com/en/2010/09/solved-anadir-claves-unicas-en-google-app-engine-en-3-lineas/
http://squeeville.com/2009/01/30/add-a-unique-constraint-to-google-app-engine/
http://csimms.botonomy.com/2012/07/there-are-only-two-ways-to-enforce-unique-constraints-in-google-app-engine.html
Have come up with the following code:
models.py: Created a separate table 'Unique' as given in the link:
class Unique(db.Model):
""" Handles uniqueness constriant on a field """
#classmethod
def unique_check(cls, form_name, field_data):
def tx(form_name, field_data):
key_name = "%s%s" % (form_name, field_data)
uk = Unique.get_by_key_name(key_name)
app.logger.debug("UK:" + str(uk))
if uk:
return False
uk = Unique(key_name=key_name)
uk.put()
return True
ret_val = db.run_in_transaction(tx, form_name, field_data)
app.logger.debug("ret_val:" + str(ret_val))
return ret_val
forms.py: I have overridden the __init__() and validate_on_submit() function in which uniqueness is checked and if it is not unique, error is attached to that field and validation error will be raised in the same way as wtforms's validators.
class TeamForm(wtf.Form):
def __init__(self, *args, **kwargs):
super(TeamForm, self).__init__(*args, **kwargs)
if kwargs.get('edit', None):
self.old_name = self.name.data.lower()
def validate_on_submit(self, edit=False):
if not super(TeamForm, self).validate_on_submit():
return False
if edit:
if self.old_name and self.old_name != self.name.data.lower():
Unique.delete_entity(self.__class__.__name__, self.old_name)
if not Unique.unique_check(self.__class__.__name__, self.name.data.lower()):
self.name.errors.append("Value '%s' is not unique" % self.name.data)
return False
else:
if not Unique.unique_check(self.__class__.__name__, self.name.data.lower()):
self.name.errors.append("Value '%s' is not unique" % self.name.data)
return False
return True
**---- Form fields declaration ----**
The above code works when new team is inserted.I mean it checks uniqueness properly. The problem occurs, when user edits the team information. Following two scenarios are problematic:
When the user tries to submit the form, application will throw "Not unique" error, it is obvious because "Unique" table has "key_name" for this team.
If user changes "team name", application has to delete the previous team name from the "Unique" table and has to check uniqueness for the "changed team name". I am not able to handle these two scenarios.
My edit_team function looks like this:
#app.route('/team/edit/<key>', methods=['GET','POST'])
#login_required
def edit_team(key):
k = db.Key(key)
team = db.get(k)
form = TeamForm(obj = team, edit=True) # to save old name, doesn't work.
if form.validate_on_submit(edit=True): # edit=True is given only in edit function
team.name = form.name.data
-- others fields are updated in the similar way --
team.put()
return redirect(url_for('teams_list'))
return render_template('edit_team.html', form=form)
Problem can be easily solved if I am able to find out 'old name' of the team, so that I can delete it from the "Unique" table. As you can see I am saving old name of the team in TeamForm __init__() function, but __init__() is called during GET(old name is saved) and also in POST(modified name will get saved!!). So, I cannot find out old name at all and it remains in the "Unique" table, nobody can use this "old team name" anymore.
I tried to explain as much as possible, please let me know if you want more info.
Edit: didn't answer your question properly the first time.
Separate instances of the Form object will be instantiated for the GET and POST requests, so you can't save the old_name to self.
You'll need to pass the old_name to the browser in the form, and have the browser submit the old_name back in the POST request.
The easyish way to do this is to create a hidden form field that the user doesn't see, but will get submitted by the POST request. I'm not too familiar with WTForms but I assume you can initialize the old_name field value in your the GET request handler.
Assuming I have a blog with entries i would like to filter optionally by category or date: For the filter I use the following form
#forms.py
class MyForm(forms.Form):
categories = forms.ModelMultipleChoiceField(Category.objects.all(),
required=False)
start_date = forms.DateField(required=False)
end_date = forms.DateField(required=False)
I ve got the following view:
#views.py
blog_entries = Blog.objects.all()
cat_filter = TurnoverFilterForm(request.GET)
if cat_filter.is_valid():
categories_chosen = cat_filter.cleaned_data['categories']
start_date = cat_filter.cleaned_data['start_date']
end_date = cat_filter.cleaned_data['end_date']
blog_entries = blog_entries.cat_filter(categories_chosen).date_filter(start_date,end_date)
return render(request,'index.html',{'blog_entries':blog_entries}
Where date_filter and cat_filter are customized manager functions (which work).
The questions are:
Do I really need to make each field in form optional? Is there any optional form for those cases? (since there is a lot of code redundancy)
i ve got an ugly if-statement in my form since the form is always valid (or at least should be as category and date range is optional and the form's request type is 'get'
Is there any other elegant solution for this sort of problems? I can imagine it is really common
If you want all the fields in the form to be optional you may override the __init__ function of the form like this:
def __init__(self, *args, **kwargs):
super(forms.Form, self).__init__(*args, **kwargs)
for f in self.fields:
self.fields[f].required=False
That way you set all the fields' required field to False and avoid code redundancy to make the whole form become optional.
Like is_valid method will return True always you may remove it from your code and add to the form another function which encapsulate the remaining code in the views.py.
With this you may simplify a little bit that code. If you want something fancier think about subclass the Form class and create OptionalForm so you can make that code reusable.
How could we make the django form to not validate if we are editing, not adding a new record. The code as following :
class PageForm(forms.Form):
name = forms.CharField(max_length=100,widget=forms.TextInput(attrs={'class':'textInput'}))
description = forms.CharField(max_length=300, required=False,widget=forms.TextInput(attrs={'class':'textInput'}))
body = forms.CharField(widget=forms.Textarea)
template = forms.CharField(max_length=30,widget=forms.TextInput(attrs={'class':'textInput'}))
navbar = forms.BooleanField(required=False, widget=forms.Select(choices=(('True','True'),
('False', 'False'))))
publish = forms.BooleanField(widget=forms.Select(choices=(('Published','Publish Now'),
('Private','Private'),
('Draft','Draft'))))
def save(self, page=None, commit=True):
data = self.cleaned_data
if not page:
page = models.Page(key_name=data['name'].replace(' ','-'))
page.name = data['name']
page.description = data['description']
page.body = data['body']
page.template = data['template']
page.publish = data['publish']
if commit: page.put()
return page
# prevent the same page 's name
def clean_name(self):
name = self.cleaned_data['name']
query = models.Page.all(keys_only=True)
query.filter('name = ', name)
page = query.get()
if page:
raise forms.ValidationError('Page name "%s" was already used before' % name)
return name
The purpose of this name validation is to prevent the records with the same name. BUt i found that, it also validate on edit, so we couldn't edit records, since it will said 'records with same name already exist'.
Actually for editing, the page param on save function wont be none, but prev record instead, and wil be none on saving a new one. But how we read this param, on clean_name function so we can now whether it is editing or creating?
Thanks a lot!
in your clean method, you can use self.initial to know whether it is adding or editing. If it is editing, the self.initial will not be empty. But when it is adding, self.initial will be dictionary of what the previous value.
If you are editing form, then the form has some instance, and you can check if that exists.
If it does, then you are probably editing existing object.. right?
Example:
If you are editing object with form, you create form object much like this:
form = MyForm(instance = myobject)
Then in your form class methods you can check if form has saved instance in a way that it is described here:
Test if Django ModelForm has instance
in your clean_name function exclude the current object from queryset
query.filter('name = ', name).exclude(pk=self.pk)
or change the if condition to check that page and current object are not the same.
Sorry, I couldn't comment below your guys post, don't know why.
#sunn0 : I didn't use django models, coz deploy the app in appengine, so use appengine model instead.
#Zayatzz : May you show a little code how to do it? Since whether we are adding or editing, we always bound the form to request.POST before validation, so don't know how to differentiate.
#Ashok : I made a workaround based on your suggestion. Since previously I didn't pass the pk to form, but passing the prev object as param instead, so couldn't exclude by using pk. So, I change the code and put additional key as pk (if create, let key empty, but if edit fill key with pk) and just check in if condition, if key field not empty, then it means we are editing. Not sure if it is best practice, but it works anyway.
I can suggest to override form's init method
https://stackoverflow.com/a/70845558/15080117
because there is an argument instance.