I'm here working with django and I'm having some problems to validate my login form. I want the text areas to appear with a default value, only for esthetic, and then I want to write the real username. The problem is that, I don't want the default value to be admitted, because it is not a correct username. Same with the password.
I post here my code:
forms.py
class AutenticacionForm(forms.ModelForm):
class Meta:
model = User
fields = ['username','password']
widgets={'password': forms.PasswordInput(),}
def __init__(self, *args, **kwargs):
super(AutenticacionForm, self).__init__(*args, **kwargs)
self.fields['username'].help_text = None
self.fields['username'].label = ""
self.fields['username'].widget.attrs.update({'required':'true','data-html':'true', 'value':'USUARIO', 'onfocus':'this.value="";', 'onblur':'if(this.value == ""){this.value = "USUARIO";}'})
self.fields['password'].label = ""
self.fields['password'].widget.attrs.update({'required':'true','data-html':'true', 'value':'PASSWORD', 'onfocus':'this.value="";', 'onblur':'if(this.value == ""){this.value = "PASSWORD";}'})
That's the way my form looks. USUARIO is just a default value, so I don't want the login to admit that value as correct.
Related
I have a form:
class SideEffectForm(ModelForm):
class Meta:
model = SideEffect
fields = ['se_name']
def __init__(self, *args, p, **kwargs):
super().__init__(*args, **kwargs)
if p == 'antipsychotic':
self.fields['se_name'].choices = [
("insomnia_and_agitation", "Insomnida and Agitation"),
("akathisia", "Akathisia"),
("dystonia", "Dystonia"),
]
That is based on this model:
class SideEffect(TimeStampedModel):
SE_CHOICES = [
("insomnia_and_agitation", "Insomnida and Agitation"),
("akathisia", "Akathisia"),
("anticholinergic_effects", "Anticholinergic Side Effects")
]
se_name = models.CharField("",max_length=200, choices=SE_CHOICES, blank=False)
concern = models.IntegerField("",default=50)
case = models.ForeignKey(Case, on_delete=models.CASCADE)
And this is the view:
class CaseView(LoginRequiredMixin, TemplateView):
model = Case
template_name = "se_balance/se_balance.html"
def get(self, *args, **kwargs):
p = self.request.GET.get("p", None)
sideeffect_formset = SideeffectFormSet(queryset=SideEffect.objects.none(),
form_kwargs={'p': p})
return self.render_to_response(
{ "page_title": p.capitalize(),
"sideeffect_formset": sideeffect_formset,
"sideeffect_formsethelper": SideEffectFormSetSetHelper,
}
)
def post(self, *args, **kwargs):
p = self.request.GET.get("p", None)
case_instance = Case(pharm_class_name=p)
sideeffect_formset = SideeffectFormSet(data=self.request.POST, form_kwargs={'p': p})
case_instance.save()
if sideeffect_formset.is_valid():
print('seform valid')
sideeffect_name = sideeffect_formset.save(commit=False)
for sideeffect in sideeffect_name:
sideeffect.case = case_instance
sideeffect.save()
return redirect(
reverse(
"results",
kwargs = {"case_id": case_instance.case_id} )
)
As it stands the form displays the first option. I would however like to have a place holder e.g. 'Please select a side effect'. I could do this by having it as one of the options (e.g. (None, 'This is the placeholder prompt')) but would prefer not to as then would need to implement measures to stop the placeholder being saved as a valid user entry. I have tried a range of suggestions on the site but none have been suitable.
You stated:
I could do this by having it as one of the options (e.g. (None, 'This is the placeholder prompt')) but would prefer not to as then would need to implement measures to stop the placeholder being saved as a valid user entry.
No, that's not true, when we set (None, 'This is the placeholder prompt') there's no need to write the custom logic for not saving selected option in the database as valid user entry.
An Excerpt from the Django-doc about this issue.
Unless blank=False is set on the field along with a default then a label containing "---------" will be rendered with the select box. To override this behavior, add a tuple to choices containing None; e.g. (None, 'Your String For Display'). Alternatively, you can use an empty string instead of None where this makes sense - such as on a CharField.
Solution:
You can simply do this (None, 'please select the side effect'), but as stated while using CharField you should use ('', 'please select the side effect') this instead and also you've already set blank=False.
Try this:
class SideEffectForm(ModelForm):
class Meta:
model = SideEffect
fields = ['se_name']
def __init__(self, *args, p, **kwargs):
super().__init__(*args, **kwargs)
if p == 'antipsychotic':
self.fields['se_name'].choices = [
('','Please select a side effect'),
("insomnia_and_agitation", "Insomnida and Agitation"),
("akathisia", "Akathisia"),
("dystonia", "Dystonia"),
]
After, doing this, see source code through Ctrl+U and you'll see below code of html:
<select ...>
<option value="" selected>please select the side effect</option>
<option value="insomnia_and_agitation">Insomnida and Agitation</option>
<option value="akathisia">Akathisia</option>
<option value="dystonia">Dystonia</option>
</select>
And selected value doesn't save in database, so if you submit form with it, you'll see the error of required.
I'm new to Django and I'm having a hard time understanding forms when the data to choose from are not taken from the database nor user input that they're generated on the go.
I currently have a template with a single ChoiceField. The data inside this field aren't fixed and they're calculated on the go once the page is requested. To calculate it I need the username of the User who is logged in. Basically, the calculation returns a list of lists in the form of ((title, id),(title,id),(title,id)), etc. that I need to put into the ChoiceField to make the User choose from one of the options.
Now, I'm not understanding how to pass the calculated list of lists to the form. I've tried to add the calculations inside the form as below but it is clearly the wrong way.
The main issue is that, to calculate my list of lists, I need the request value, and I don't know how to access it from the form.
Another idea was to add the generate_selection function inside the init but then I don't know how to pass main_playlist to being able to add it to ChoiceField
Below my not working forms.py
forms.py
class ChoosePlaylistForm(forms.Form):
playlists = forms.ChoiceField(choices=HERE_SHOULD_GO_main_playlist)
def generate_selection(self):
sp_auth, cache_handler = spotify_oauth2(self.request)
spotify = spotipy.Spotify(oauth_manager=sp_auth)
user_playlists = spotify.current_user_playlists(limit=10)
main_playlist = []
for playlists in user_playlists["items"]:
playlists_list = []
playlists_list.append(playlists['name'])
playlists_list.append(playlists['id'])
main_playlist.append(playlists_list)
return main_playlist
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(ChoosePlaylistForm, self).__init__(*args, **kwargs)
class Meta:
model = User
fields = ('playlists',)
The views should be something like below so I'm able to pass the request
views.py
form = ChoosePlaylistForm(request=request)
Maybe overriding the field choices in the form constructor would work:
class ChoosePlaylistForm(forms.Form):
playlists = forms.ChoiceField(choices=())
class Meta:
model = User
fields = ('playlists',)
def __init__(self, *args, request=None, **kwargs):
super(ChoosePlaylistForm, self).__init__(*args, **kwargs)
self.request = request
self.fields['playlists'].choices = self.generate_selection()
def generate_selection(self):
sp_auth, cache_handler = spotify_oauth2(self.request)
spotify = spotipy.Spotify(oauth_manager=sp_auth)
user_playlists = spotify.current_user_playlists(limit=10)
choices = []
for playlist in user_playlists["items"]:
playlist_choice = (playlist["name"], playlist["id"])
choices.append(playlist_choice)
return choices
I'm building an Edit form for a model in my database using a ModelForm in Django. Each field in the form is optional as the user may want to only edit one field.
The problem I am having is that when I call save() in the view, any empty fields are being saved over the instance's original values (e.g. if I only enter a new first_name, the last_name and ecf_code fields will save an empty string in the corresponding instance.)
The form:
class EditPlayerForm(forms.ModelForm):
class Meta:
model = Player
fields = ['first_name', 'last_name', 'ecf_code']
def __init__(self, *args, **kwargs):
super(EditPlayerForm, self).__init__(*args, **kwargs)
self.fields['first_name'].required = False
self.fields['last_name'].required = False
self.fields['ecf_code'].required = False
The view:
def view(request, player_pk = ''):
edit_player_form = forms.EditPlayerForm(auto_id="edit_%s")
if "edit_player_form" in request.POST:
if not player_pk:
messages.error(request, "No player pk given.")
else:
try:
selected_player = Player.objects.get(pk = player_pk)
except Player.DoesNotExist:
messages.error(request, "The selected player could not be found in the database.")
return redirect("players:management")
else:
edit_player_form = forms.EditPlayerForm(
request.POST,
instance = selected_player
)
if edit_player_form.is_valid():
player = edit_player_form.save()
messages.success(request, "The changes were made successfully.")
return redirect("players:management")
else:
form_errors.convert_form_errors_to_messages(edit_player_form, request)
return render(
request,
"players/playerManagement.html",
{
"edit_player_form": edit_player_form,
"players": Player.objects.all(),
}
)
I've tried overriding the save() method of the form to explicitly check which fields have values in the POST request but that didn't seem to make any difference either.
Attempt at overriding the save method:
def save(self, commit = True):
# Tried this way to get instance as well
# instance = super(EditPlayerForm, self).save(commit = False)
self.cleaned_data = dict([ (k,v) for k,v in self.cleaned_data.items() if v != "" ])
try:
self.instance.first_name = self.cleaned_data["first_name"]
except KeyError:
pass
try:
self.instance.last_name = self.cleaned_data["last_name"]
except KeyError:
pass
try:
self.instance.ecf_code = self.cleaned_data["ecf_code"]
except KeyError:
pass
if commit:
self.instance.save()
return self.instance
I also do not have any default values for the Player model as the docs say the ModeForm will use these for values absent in the form submission.
EDIT:
Here is the whole EditPlayerForm:
class EditPlayerForm(forms.ModelForm):
class Meta:
model = Player
fields = ['first_name', 'last_name', 'ecf_code']
def __init__(self, *args, **kwargs):
super(EditPlayerForm, self).__init__(*args, **kwargs)
self.fields['first_name'].required = False
self.fields['last_name'].required = False
self.fields['ecf_code'].required = False
def save(self, commit = True):
# If I print instance variables here they've already
# been updated with the form values
self.cleaned_data = [ k for k,v in self.cleaned_data.items() if v ]
self.instance.save(update_fields = self.cleaned_data)
if commit:
self.instance.save()
return self.instance
EDIT:
Ok so here is the solution, I figured I'd put it here as it might be useful to other people (I've certainly learned a bit from this).
So it turns out that the is_valid() method of the model form actually makes the changes to the instance you pass into the form, ready for the save() method to save them. So in order to fix this problem, I extended the clean() method of the form:
def clean(self):
if not self.cleaned_data.get("first_name"):
self.cleaned_data["first_name"] = self.instance.first_name
if not self.cleaned_data.get("last_name"):
self.cleaned_data["last_name"] = self.instance.last_name
if not self.cleaned_data.get("ecf_code"):
self.cleaned_data["ecf_code"] = self.instance.ecf_code
This basically just checks to see if the fields are empty and if a field is empty, fill it with the existing value from the given instance. clean() gets called before the instance variables are set with the new form values, so this way, any empty fields were actually filled with the corresponding existing instance data.
You could maybe use the update() method instead of save()
or the argument update_field
self.instance.save(update_fields=['fields_to_update'])
by building the list ['fields_to_update'] only with the not empty values.
It should even work with the comprehension you've tried :
self.cleaned_data = [ k for k,v in self.cleaned_data.items() if v ]
self.instance.save(update_fields=self.cleaned_data)
EDIT :
Without overriding the save method (and commenting out this attempt in the form):
not_empty_data = [ k for k,v in edit_player_form.cleaned_data.items() if v ]
print(not_empty_data)
player = edit_player_form.save(update_fields=not_empty_data)
You could check the values if it's not empty in your view without overriding save()
if edit_player_form.is_valid():
if edit_player_form.cleaned_data["first_name"]:
selected_player.first_name = edit_player_form.cleaned_data["first_name"]
if edit_player_form.cleaned_data["last_name"]:
selected_player.last_name= edit_player_form.cleaned_data["last_name"]
if edit_player_form.cleaned_data["ecf_code"]:
selected_player.ecf_code= edit_player_form.cleaned_data["ecf_code"]
selected_player.save()
This should work fine with what you want. I'm not sure if it's the best way to do it but it should work fine.
I am working on a Python/Django project, and one of the fields on a form is currently broken (it's a datetimepicker field- the issue is that you can't select dates beyond 01/01/2017). It seems this is because the library being used to add the datetimepicker is no longer supported).
I will look into implementing a datetimepicker myself, but need to allow the user to select dates beyond the 01/01/2017 on this form- in order to save the dates and times for scheduled meetings, etc.
My thoughts for the moment are to create a temporary fix, by adding simple EasyText fields to the form for the date/ time. I am aware that this means that the data entered will not be validated, so there is room for user input error, but it will enable the user to set dates/ times for meeting at least temporarily, until I fix the issue with the current datetimepicker field.
The form that I want to add these fields to is defined in forms.py with:
class PostDepMeetingForm(ValidatedForm):
postdep_meeting_date = MoonDateTimeField(required=False, widget=forms.DateTimeInput(format='%d/%m/%Y %H:%M', attrs=({'class':'datetimepicker'})))
planning_who = forms.CharField(required=False)
general_notes = EasyText(label='Survey notes')
#ERF(16/12/2016 # 1345) Add a text area for the meeting date & time
meeting_date_time = EasyText(label='Meeting date & time')
class Meta:
model = Survey
fields = ('postdep_meeting_date', 'planning_who','building_regs','general_notes', 'meeting_date_time')
def __init__(self, *args, **kwargs):
instance = kwargs.get('instance', {})
project = instance.project
try:
postdep_meeting = project.meetings.get(purpose=3)
self.postdep_meeting_id = postdep_meeting.id
self.postdep_meeting_creator = postdep_meeting.event_creator or ''
postdep_meeting_date = postdep_meeting.date
general_notes = postdep_meeting.notes
#ERF(16/12/2016 # 1420) Try setting the meeting_date_time EasyText's default value to postdep_meeting.date & postdep_meeting.time
#meeting_date_time
except ObjectDoesNotExist:
postdep_meeting_date = None
general_notes = None
try: planning_who = project.assigned.select_related('employee').get(role=Role.PL).employee.id
except ObjectDoesNotExist: planning_who = None
initial = kwargs.get('initial', {})
initial={
'postdep_meeting_date': postdep_meeting_date,
'general_notes': general_notes,
'planning_who': planning_who,
}
kwargs['initial'] = initial
super(PostDepMeetingForm, self).__init__(*args, **kwargs)
self.fields['planning_who'] = AutoFlexiSelect(model='e', choices=get_choices('ARCHITECT_CHOICES') + [('*', 'Other')], label="Who", current_id=planning_who,)
self.fields['postdep_meeting_date'].widget.attrs.update({'data-meeting-id': getattr(self,'postdep_meeting_id', ''), 'data-meeting-creator': getattr(self,'postdep_meeting_id', '')})
def save(self, commit=True):
project = self.instance.project
data = self.cleaned_data
if 'postdep_meeting_date' in self.changed_data:
postdep_meeting = Meeting.objects.get_or_create(project=project, purpose=3)[0]
postdep_meeting.date = data['postdep_meeting_date']
postdep_meeting.notes = data['general_notes']
postdep_meeting.save()
if data['planning_who']:
try: project.assigned.select_related('employee').get(role=Role.PL).delete()
except ObjectDoesNotExist: pass
pe = ProjectEmployee.objects.create(role=Role.PL, project=project, employee_id=data['planning_who'])
return super(PostDepMeetingForm, self).save(commit=commit)
I have added the EasyText field, called meeting_date_time to the form, and can see it displayed on the form when I view the page in the browser.
What I'm unsure about how to do now- is save any input entered by the user, and have whatever they have input into that field displayed there automatically the next time they browse to that page... Will I need to add a new field to the project model to save this information?
Edit
As requested in a comment, ValidatedForm is defined with:
class ValidatedForm(forms.ModelForm):
error_css_class = 'error-field'
required_css_class = 'required'
def __init__(self, *args, **kwargs):
super(ValidatedForm, self).__init__(*args, **kwargs)
# for k, field in self.fields.items():
# if 'required' in field.error_messages:
# # print 'field error', field, field
# # field.widget.attrs['class'] = 'error-field'
# print 'field error', k
# field.error_messages['required'] = '*'
# else:
# print 'Field erroR', field
and EasyText is a 'text area' for storing user input on the form, defined with:
def EasyText(field_class="medium", readonly=False, placeholder='', extras={}, **kwargs):
textarea = forms.CharField(widget=TextareaWidget(attrs={'class':field_class, 'readonly':readonly, 'placeholder': placeholder}))
if extras:
textarea.widget.attrs.update(**extras)
try: textarea.label = kwargs['label']
except KeyError: pass
try: textarea.required = kwargs['required']
except KeyError: textarea.required = False
return textarea
For this particular administration page, I'd like to turn the 'current value' (outlined in a red circle) into a link going back to the administration page for this particular object.
But I can't find where to go to make this change. I know that I need to somehow override how this
is displayed but I can't figure it out.
What do I need to override to do what I want?
Admin model definition:
class FirmwareConfigElementsChoiceInline(admin.TabularInline):
model = FirmwareConfigElements
extra = 1
class FirmwareConfigAdmin(admin.ModelAdmin):
save_as = True
list_display = ('name', 'description')
inlines = [FirmwareConfigElementsChoiceInline]
Using Filip's great help I've gotten to this:
class FirmwareConfigElementsForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
klass = FirmwareConfigElementsForm
super(klass, self).__init__(*args, **kwargs)
if self.instance.type == 'incfw':
value = self.instance.value
url = '#' # TODO: get the URL for the value
hyperlink = '%s' % (url, value)
label = self.fields['type'].label.replace(value, hyperlink)
self.fields['type'].label = label
But in the above code, self.fields['type'].label has the contents Type and not Include another FW Config - BASE:IBM-HS22/HS22V as I was expecting.
I've explored it in the debugger but I can't figure out how to get to the particular label that I want to change.
Inline admin models have a template property you can use to supply a custom template. From there, you'll need to modify the code to add the url.
You'll need to provide a custom ModelForm for the FirmwareConfigElements model, which you'll set as the value for the FirmwareConfigElementsChoiceInline.form class attribute.
Here you'll want to override the ModelForm.__init__() instance method to assign a new label for the field you want to override if the form is bound:
class FirmwareConfigElementsForm(models.ModelForm):
def __init__(self, *args, **kwargs):
klass = FirmwareConfigElementsForm
super(klass, self).__init__(*args, **kwargs)
if form.is_bound and 'value' in self.data:
value = self.data['value']
url = '' # TODO: get the URL for the value
hyperlink = '%s' % (url, value)
label = self.fields['type'].label.replace(value, hyperlink)
self.fields['type'].label = label
class FirmwareConfigElementsChoiceInline(admin.TabularInline):
model = FirmwareConfigElements
extra = 1
form = FirmwareConfigElementsForm
Now, if you want the label to change dynamically as the user changes the form data, then it gets a lot uglier and you'll have to resort to referencing JavaScript media and performing the above on the fly.