About disabling autocomplete in UserCreationForm - python

I long searched the internet and this site looking for a solution but nothing work. The problem is that in any registration form I make in Django, each time a user click on a username or password field, a list of previously entered values is shown and this behaviour is not desirable.
The raw fact is that, if I directly comment this line
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
in
# UserCreationForm, auth/form
password1 = forms.CharField(
label=_("Password"),
strip=False,
#widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
help_text=password_validation.password_validators_help_text_html(),
)
everything work as I want including the username.
But I don't want to touch the Django UserCreationForm in auth/forms. The ideal would be subclass it, and customize it. Here what I did
class CustomUserCreationForm(UserCreationForm):
def __init__(self, *args, **kwargs):
super(CustomUserCreationForm, self).__init__(*args, **kwargs)
# prevents the form to automatically autofocus
self.fields['email'].widget.attrs.pop("autofocus")
self.fields['username'].widget.attrs.pop("autocomplete")
#self.fields['password1'].widget.attrs.pop("autocomplete")
#self.fields['password1'].widget.attrs.update({'autocomplete':'off', 'maxlength':'32'})
...
I tried it with many combinations, including with new-password, off, None, or even empty strings, everything is ignored.
I repeat, the only way to not autocomplete the username and password fields is to comment the widget line in the original class, but this would be a very bad idea and, especially, it would break every time I will upgrade Django.
Any other reasonable solutions?

I once had a registration form I wanted to get this same behaviour with... What worked for me was, for example:
forms.py
class RegistrationForm(forms.Form):
username = forms.CharField(widget=forms.TextInput(attrs={..., 'autocomplete': 'new-password'}))
email= forms.EmailField(widget=forms.EmailInput(attrs={..., 'autocomplete': 'new-password'}))
password = forms.CharField(widget=forms.PasswordInput(attrs={..., 'autocomplete': 'new-password'}))
# Setting the autocomplete='off' from within the form class by using {'autocomplete': 'new-password'} to remove the suggested list over/below the input field
In your case, probably you could try...
class CustomUserCreationForm(UserCreationForm):
def __init__(self, *args, **kwargs):
super(CustomUserCreationForm, self).__init__(*args, **kwargs)
self.fields['username'].widget.attrs.update({'autocomplete': 'new-password'})
self.fields['email'].widget.attrs.update({'autocomplete': 'new-password'})
self.fields['password1'].widget.attrs.update({'autocomplete': 'new-password'})
Also note that you'd have to add autocomplete="off" on your form tag as well.
<form method="POST" autocomplete="off">
...
...
</form>
Disclaimer:
While the username and email fields aren't displaying the suggested list anymore, the password field continues for many persons. In my case it did, so as a work around I changed up some stuff to the following...
# Instead of a password field, I've changed it to a text field
# Setting onkeyup event that will replace the text to asterisks
password = forms.CharField(widget=forms.TextInput(attrs={'id': 'password', 'autocomplete': 'new-password', 'onkeyup': 'hideCharacters()'}))
Within the html file:
<form method="POST" autocomplete="off">
...
...
</form>
# This script can be within a javascript file or the html: up to you...
# Script to replace text from field to asterisk(s).
<script type="text/javascript">
function hideCharacters() {
var x = document.getElementById("password");
x.value = x.value.replace(/./g, "*");
}
</script>
In a nutshell, that did the job for me.

Related

Create a confirmation message from model in Django

I have an app in Django that has multiple models. I have a particular model like this:
models.py
class MyModel(models.Model):
model_id= models.AutoField(primary_key=True)
model_date = models.DateTimeField(verbose_name="Label 1")
model_counter = models.IntegerField(blank=True, null=True)
admin.py
class MyModelAdmin(admin.ModelAdmin):
list_display = ('model_id', 'model_date ', 'model_counter ')
list_filter = (
('model_date', DropdownFilter)
)
def has_delete_permission(self, request, obj=None):
return False
def get_actions(self, request):
actions = super().get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
readonly_fields = ['model_counter ']
admin.site.register(MyModel, MyModelAdmin)
What I need is that the user confirm that he wants to save the model, even if the date is greater than today, in doing so the user can create a model that can be wrong for other users, but sometime it's correct that the date is greater than today.
I cannot use a custom HTML form, only the default one.
The user must interact someway with the page, and give direct aknowledgement that he knows that is saving a new model that can be dangerous for other users. The user must be able to abort the saving or modify the date.
So I tried to use another field of the model to store the counter:
def clean(self):
condition = False
if self.model_counter is None:
condition = True
else:
condition = self.model_counter == 1
if condition :
self.model_counter = 1
raise ValidationError("Attention, the date inserted is after the current date, click the SAVE button another time to proceed with the saving")
As a counter I use another field of the same model. I'm not able to make the updating of the counter working. From what I have understood, the lifecycle of validation prevent me to alter in the proper way the state of the istance of the model that the code it's saving, so the updating of the field model it's ignored.
There are is any way to achieve my goal? I used the model field for storing the value of counter because I'm not able in another way. I don't care where is the counter. I don't care also to use the message system of Django or a popup. I need only to force the user under some condition to make an interaction that force him to continue or abort the saving.
Edit
I added also the code in the admin.py for more clearness. I modified only the models and the admin, invoke the command: python3 manage.py inspectdb > models.py and I got all the code generated. That it's the standard procedure for this things in my company. So I cannot add (or I don't how) code to the Web pages generated from Django.
I think you would be best to use some JavaScript here. Where you add a click event to the submit button where a modal/dialag asks the user to confirm. If they say Yes, then you can submit the form.
For example with dialog (you can make it modal if you want):
HTML
<form id="myModelForm" method="POST">
{% csrf_token %}
{{ form.as_p }}
<button id="myModelSave">{% trans "Save" %}</button>
</form>
JS
let saveButton = document.getElementById('myModelSave');
saveButton.onclick = function() {
let accepted = confirm("Are you sure you want to save?");
if (accepted) {
document.getElementById('myModelForm').submit();
}
}

Django-taggit tag value retrieval and formatting failing

I am trying to implement a tagging process for profiles so you can add your hobbies for example.
I have chosen django-taggit as it seemed quite simple and does what I need it to, plus don't really know how to do it myself from scratch.
I have managed to make it work to some extent but I am having issues with 3 things:
Not really sure what's the best way to control the form field for these tags as I generate the form automatically with widget adjustments in meta function of the form, but it might work fine after resolving the below two issues.
When there is no data for the field hobbies (tags) the field gets populated with a single tag of value "[]" as per below image.
When I add a tag of "music" and submit the form after I reload the page I get this "[]" as per image. I assumed this will be dealt with by the library, but I cannot see another similar scenario online.
When I try adding another tag of "games" and save and reload, the below happens. The initial value gets wrapped again.
My model is:
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
hobbies = TaggableManager()
My form is:
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ['hobbies',]
def __init__(self, *args, **kwargs):
super(UserProfileForm, self).__init__(*args,**kwargs)
self.fields['hobbies'].widget = forms.TextInput()
self.fields['hobbies'].widget.attrs['data-role'] = "tagsinput"
self.fields['hobbies'].widget.attrs['class'] = "form-control"
self.fields['hobbies'].required = False
My view function is:
if request.method == 'POST':
user_profile = UserProfile.objects.get(user=request.user)
form = UserProfileForm(request.POST, instance=user_profile)
print(form)
if form.is_valid():
obj = form.save(commit=False)
obj.user = request.user
obj.save()
print("Form valid")
form.save_m2m()
Using:
<script src="/static/js/tagsinput.js"></script>
<link rel="stylesheet" href="{% static 'css/tagsinput.css' %}" />
I had this exact same problem.
One solution is to apply the data-role="tagsinput" AFTER you turn a list of tags into a comma-separated string for the form.
Here is that solution:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, **kwargs):
self.fields['tags'].widget.attrs['value'] = ", ".join(list(self.instance.tags.names()))
self.fields['tags'].widget.attrs['data-role'] = "tagsinput"
Output:
As you can see, there's a problem with quotes appearing around tags that are multi-word. It also causes new tags with quotes to be saved to the database.
If double-quotes didn't appear around multi-word phrases, this would be the most elegant solution. If someone solves this in the future, drop a note!
My template is this:
<div class="m-3 p-3 border">
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-primary" type="submit">Save Form</button>
</form>
</div>
I know I can use a template tag to strip the extra quotes from the tag field itself, but then I'd have to go through and create all the form fields manually just to set the tags template tag.
For the time being, my solution is to simply use Javascript and just modify the Meta widgets section of the form.
FINAL ANSWER (for now):
forms.py
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
widgets = {
'tags': forms.TextInput(attrs={
"data-role": "tagsinput",
})
}
custom.js - put this script on the page that loads the form.
document.addEventListener("DOMContentLoaded", function(event) {
let tags_input = document.querySelector('#id_tags');
let tags_input_value = tags_input.value;
let new_value = [...tags_input_value.matchAll(/<Tag:\s*([\w\s]+)>/g)].map(([, m]) => m).join(', ')
tags_input.setAttribute('value', new_value);
}
So all we're doing is modifying the front-end presentation, and leaving all the backend internal forms functionality untouched.
So after quite a few (hundreds) of tests, I finally narrowed down where the issue was and tried to go around it with successful result.
It seems the data got amended into tag objects through tagsinput library I was using. Only when the "data-role" was specified as "tagsinput" in the forms.py the data would already come to html side as those objects and be shown incorrectly. So instead I wanted to keep the data clean and only apply data-role='tagsinput' in the end for visual aspect, which I did using:
var hobbiesTags = document.getElementById("id_hobbies");
if(hobbiesTags){
var att = document.createAttribute("data-role");
att.value = "tagsinput";
hobbiesTags.setAttributeNode(att);
};
And that resulted in the below. Maybe there are better ways to do this, I'm not sure, but it's a pretty clean solution. Share your alternatives.

How to use html form 'placeholder' property with standard view?

I am new to Django and am using Django's user auth package (django.contrib.auth) for user login, password reset, etc.
Now, while everything works just fine, on the logon form, I'd like to use the html-placeholder property. How can I use / populate this? I did find some answers (e.g. this one) but I do not understand where to place this code, how to extend the view / form or even the model (e.g. adding new fields) as this gets delivered with the standard package.
Right now, I have added the following:
forms.py
from django import forms
from django.contrib.auth.forms import AuthenticationForm
class LoginForm(forms.Form):
username = forms.CharField(label='username')
password = forms.CharField(label='password')
def __init__(self, *args, **kwargs):
super(LoginForm, self).__init__(*args, **kwargs)
self.fields['username'].widget.attrs['placeholder'] = 'Username'
self.fields['password '].widget.attrs['placeholder'] = 'Password'
I am not sure what I need to do in urls.py or models.py or anywhere else for the code to be executed.
I found the following solution:
installed bootstrap4
using the following tag in my html:
{% bootstrap_field form.password field_class="field" placeholder="Password" show_label=False %}
I believe by adding the widgets to your forms init, when you instantiate the form in your template the placeholder should appear like intended. Did you just try rendering your form such as {{form}} in the template and see if it rendered it? I dont think bootstrap4 is necessary but it is a great tool.

Hidden Field Flow Using Django

I'm trying to receive a POST then generate a form with new fields and pass along the values I received in the previous POST as hidden variables. I've done a lot of searching in documentation and can't seem to find anything that connects the two sides of this flow. I'm using Django 1.4 w/ Python 2.7.
views.py
from django.shortcuts import render_to_response
from gateway_interface.forms import newForm
def requestNewForm(request):
if (request.method == "POST"):
form = newForm(request)
return render_to_response('myTemplate.html', {'form' : form})
forms.py
from django import forms
class newForm(forms.Form):
def __init__(self, request):
my_passed_variable = request.POST['pass_variable']
a_new_variable = forms.CharField(max_length = 25)
my_passed_variable = forms.CharField(widget = forms.HiddenInput())
myTemplate.html
<form action="/myNextDjangoView/" method="post">
<div class="fieldWrapper">
I need this value: {{ form.a_new_variable }} <br>
</div>
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
<input type="submit" value="Submit">
</form>
I must be doing something fundamentally wrong. If I use the for loop in the template none of the visible fields show on the page. Nothing I've tried has caused the hidden fields to populate.
Any suggestions? Perhaps I'm missing an import somewhere? Is there something I need to import in forms.py to allow for the use of HiddenInput()?
EDIT 1:
I've modified forms.py to look like this:
form django import forms
class newForm(forms.Form):
a_new_variable = forms.CharField(max_length = 25)
my_passed_variable = forms.CharField(widget = forms.HiddenInput())
def __init__(self, *args, **kwargs):
super(newForm, self).__init__(*args, **kwargs)
This has not changed my output. I still get the same form with no fields showing (hidden or visible). What I need to do is instantiate with an initialization dictionary. (I think?) Where the dictionary contains the name and values for all the hidden fields.
initial_dict = { 'my_passed_variable' : request.POST.get('pass_variable') }
form = newForm(initial = initial_dict)
EDIT 2:
Using the initialization dictionary was a step in the right direction! I am now able to see the visible fields in my form but the hidden fields are still not populating.
views.py
from django.shortcuts import render_to_response
from gateway_interface.forms import newForm
def requestNewForm(request):
if (request.method == "POST"):
initial_dict = { 'my_passed_variable' : request.POST.get('pass_variable') }
form = newForm(initial = initial_dict)
return render_to_response('myTemplate.html', {'form' : form})
EDIT 3:
I've got it working. Thanks to Jordan Reiter for pushing me in the right direction. It turns out the problem was almost entirely the caching of my browser after EDIT 1 above. I moved to Chrome's incognito mode and everything just worked.
There is a definite problem with this code:
from django import forms
class newForm(forms.Form):
def __init__(self, request):
my_passed_variable = request.POST['pass_variable']
a_new_variable = forms.CharField(max_length = 25)
my_passed_variable = forms.CharField(widget = forms.HiddenInput())
First, it's incredibly confusing for there to be two variables with identical names (although one of them is self.my_passed_variable available throughout the form and the other is just my_passed_variable available in __init__ only). I can't help but think you're trying to tie the two variables together somehow, but you're not. Worst/best case scenario (if you rewrote my_passed_variable = request.POST['pass_variable'] as self.my_passed_variable = request.POST['pass_variable']) you're overwriting the value for form field object with a string.
Second, I'm assuming you snipped out a bunch of code from the __init__ function. You're missing the super which actually makes this a form. As it stands, the form object is not going to be instantiated correctly.
If you're trying to do what I think you're trying to do, you want to rewrite it this way:
from django import forms
class newForm(forms.Form):
# first, I'm going to put the fields at the top, I think that's more standard
a_new_variable = forms.CharField(max_length = 25)
my_passed_variable = forms.CharField(widget = forms.HiddenInput())
def __init__(self, request, *args, **kwargs):
super(newForm, self).__init__(*args, **kwargs)
self.fields['my_passed_variable'].initial = request.POST.get('pass_variable') # don't assume the variable is present!

How to implement a "back" link on Django Templates?

I'm tooling around with Django and I'm wondering if there is a simple way to create a "back" link to the previous page using the template system.
I figure that in the worst case I can get this information from the request object in the view function, and pass it along to the template rendering method, but I'm hoping I can avoid all this boilerplate code somehow.
I've checked the Django template docs and I haven't seen anything that mentions this explicitly.
Actually it's go(-1).
<input type=button value="Previous Page" onClick="javascript:history.go(-1);">
This solution worked out for me:
Go back
But that's previously adding 'django.core.context_processors.request', to TEMPLATE_CONTEXT_PROCESSORS in your project's settings.
Back
Here |escape is used to get out of the " "string.
Well you can enable:
'django.core.context_processors.request',
in your settings.TEMPLATE_CONTEXT_PROCESSORS block and hook out the referrer but that's a bit nauseating and could break all over the place.
Most places where you'd want this (eg the edit post page on SO) you have a real object to hook on to (in that example, the post) so you can easily work out what the proper previous page should be.
You can always use the client side option which is very simple:
Back
For RESTful links where "Back" usually means going one level higher:
<input type="button" value="Back" class="btn btn-primary" />
All Javascript solutions mentioned here as well as the request.META.HTTP_REFERER solution sometimes work, but both break in the same scenario (and maybe others, too).
I usually have a Cancel button under a form that creates or changes an object. If the user submits the form once and server side validation fails, the user is presented the form again, containing the wrong data. Guess what, request.META.HTTP_REFERER now points to the URL that displays the form. You can press Cancel a thousand times and will never get back to where the initial edit/create link was.
The only solid solution I can think of is a bit involved, but works for me. If someone knows of a simpler solution, I'd be happy to hear from it. :-)
The 'trick' is to pass the initial HTTP_REFERER into the form and use it from there. So when the form gets POSTed to, it passes the correct, initial referer along.
Here is how I do it:
I created a mixin class for forms that does most of the work:
from django import forms
from django.utils.http import url_has_allowed_host_and_scheme
class FormCancelLinkMixin(forms.Form):
""" Mixin class that provides a proper Cancel button link. """
cancel_link = forms.fields.CharField(widget=forms.HiddenInput())
def __init__(self, *args, **kwargs):
"""
Override to pop 'request' from kwargs.
"""
self.request = kwargs.pop("request")
initial = kwargs.pop("initial", {})
# set initial value of 'cancel_link' to the referer
initial["cancel_link"] = self.request.META.get("HTTP_REFERER", "")
kwargs["initial"] = initial
super().__init__(*args, **kwargs)
def get_cancel_link(self):
"""
Return correct URL for cancelling the form.
If the form has been submitted, the HTTP_REFERER in request.meta points to
the view that handles the form, not the view the user initially came from.
In this case, we use the value of the 'cancel_link' field.
Returns:
A safe URL to go back to, should the user cancel the form.
"""
if self.is_bound:
url = self.cleaned_data["cancel_link"]
# prevent open redirects
if url_has_allowed_host_and_scheme(url, self.request.get_host()):
return url
# fallback to current referer, then root URL
return self.request.META.get("HTTP_REFERER", "/")
The form that is used to edit/create the object (usually a ModelForm subclass) might look like this:
class SomeModelForm(FormCancelLinkMixin, forms.ModelForm):
""" Form for creating some model instance. """
class Meta:
model = ModelClass
# ...
The view must pass the current request to the form. For class based views, you can override get_form_kwargs():
class SomeModelCreateView(CreateView):
model = SomeModelClass
form_class = SomeModelForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["request"] = self.request
return kwargs
In the template that displays the form:
<form method="post">
{% csrf token %}
{{ form }}
<input type="submit" value="Save">
Cancel
</form>
For a 'back' button in change forms for Django admin what I end up doing is a custom template filter to parse and decode the 'preserved_filters' variable in the template. I placed the following on a customized templates/admin/submit_line.html file:
<a href="../{% if original}../{% endif %}?{{ preserved_filters | decode_filter }}">
{% trans "Back" %}
</a>
And then created a custom template filter:
from urllib.parse import unquote
from django import template
def decode_filter(variable):
if variable.startswith('_changelist_filters='):
return unquote(variable[20:])
return variable
register = template.Library()
register.filter('decode_filter', decode_filter)
Using client side solution would be the proper solution.
Cancel

Categories