I am using Django rest framework 3.1 and django-rest-swagger 0.3.2. Things are working fairly well however I am having an issue with the password input fields in my login serializer. My login serializer is pretty simple, it inherits from rest_framework.authtoken.serializers.AuthTokenSerializer:
class MyLoginSerializer(AuthTokenSerializer):
def validate(self, attrs):
# small amount of validation logic here
The AuthTokenSerializer has the password field defined with the proper style:
class AuthTokenSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField(style={'input_type': 'password'})
But Swagger displays the password input in the POST documentation as plain text (input type='text'). I'm not sure what is going wrong here, shouldn't django-rest-swagger be able to interpret the style here and render the input accordingly? Am I missing something? Any advice is appreciated, thanks much!
I'm still using DRF 2.4.4, but this is what I have to * out the field:
from django.forms.widgets import PasswordInput
class UserSerializer(serializers.HyperlinkedModelSerializer):
password = serializers.CharField(
required=False,
write_only=True, # <- never send password (or hash) to the client
allow_none=True, # <- allow users to not specify a password
blank=True,
# this creates a password input field for DRF-only
# front end (not sure about swagger)
# vvvvvvvvvvvvvvvvvv
widget=PasswordInput
)
class Meta:
model = User
fields = ('url', 'id', 'username', 'password', [...]
Related
Given some forms in Django (take the following for simplicity)
class LoginRegisterForm(forms.Form):
email = forms.EmailField(label="Email", max_length=100)
password = forms.CharField(label="Password", widget=forms.PasswordInput(), max_length=100)
We're trying to restrict the submission of these forms if there are additional fields submitted so we have the following added method to our form class
def correct_fields(self):
return set(self.data.keys()) == {"Email", "Password"}
And the following code in the views.py method corresponding to the submission of this form:
if form.is_valid() and form.correct_fields:
How can we avoid having to write Email and Password in both the definition of the form and the correct_fields method? Does django offer a build-in function to prevent forms being submitted if the correct fields are not submitted? (The form is still submitted if the correct fields and some additional fields are given). If this functionality is not given, how do I avoid writing the fields twice?
Fields where you do not specify required=False are required. As the documentation on the required=… parameter [Django-doc] says:
By default, each Field class assumes the value is required, so if you pass an empty value – either None or the empty string ("") – then clean() will raise a ValidationError exception.
So it will already validate that data indeed contains email and password. You can define optional fields with:
class LoginRegisterForm(forms.Form):
email = forms.EmailField(label="Email", max_length=100)
password = forms.CharField(label="Password", widget=forms.PasswordInput(), max_length=100)
info = forms.CharField(label="Tell us someting", required=False, intial='')
My Question - How to create Custom Text Field within the django/contrib/auth/forms.py. ?
Am trying to tweak the Django default User Model . Am adding a test field by the name "bio"
My code so far in the /python3.6/site-packages/django/contrib/auth/forms.py file is as below -
class UserCreationForm(forms.ModelForm):
"""
A form that creates a user, with no privileges, from the given username and
password.
"""
error_messages = {
'password_mismatch': _("The two password fields didn't match."),
}
password1 = forms.CharField(
label=_("Password"),
strip=False,
widget=forms.PasswordInput,
help_text=password_validation.password_validators_help_text_html(),
)
password2 = forms.CharField(
label=_("Password confirmation"),
widget=forms.PasswordInput,
strip=False,
help_text=_("Enter the same password as before, for verification."),
)
bio = forms.CharField( #FOO_bio # this value --not getting saved in PSQL
label=_("bio_test within Forms.py"),
#widget=forms.PasswordInput, #Didnt work thus switched to forms.TextInput
widget=forms.TextInput,
strip=False,
help_text=_("Enter some dummy BIO here ."),
)
Further down in this file within the defined method - def clean_password2(self) , am trying to add the "bio" as
def clean_password2(self):
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
bio = self.cleaned_data.get("bio") # I have no clue how to do this ?
print(bio) #prints None in the terminal
I do understand there is no key by the name "bio" within the DICT - cleaned_data.
In the file - /site-packages/django/contrib/auth/models.py , have added the "bio" as a models.TextField , within the class class AbstractUser(AbstractBaseUser, PermissionsMixin):
bio = models.TextField(
_('bio'),
max_length=500, blank=True)
The custom field "bio" shows up in the Registration form , the test user enters a text value - the form gets submitted , but nothing gets saved in the psql for "bio". A new user is registered and can be seen both in - psql at the terminal and within the Django Admin .
Also within the Django admin - when i go to the URL - http://digitalcognition.co.in/admin/auth/user/16/change/ , i can see a Text Field named "Bio" within the "Personal Info" section , but that again is blank . The "Email Address" , "UserName" and "Password" are seen as usual .
Don't modify the default User model. As mentioned in the documentation:
If you wish to store information related to User, you can use a
OneToOneField to a model containing the fields for additional
information. This one-to-one model is often called a profile model, as
it might store non-auth related information about a site user.
And you definitely shouldn't modify django code. Just create a new form that inherits from django's UserCreationForm, and add your fields there.
See the accepted answer here.
I am just starting to play around with Django REST Framework for a side project, and I am trying to force all usernames to be lowercase (since by default they are case sensitive and I don't want people to have usernames like kevin and Kevin for example).
This is the code I am using to create a user:
# The serializer
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
token = serializers.SerializerMethodField()
def create(self, validated_data):
user = get_user_model().objects.create(**validated_data)
user.set_password(validated_data['password'])
user.save()
return user
def get_token(self, user):
token = Token.objects.create(user=user)
return token.key
class Meta:
model = get_user_model()
fields = ('username', 'first_name', 'last_name', 'email', 'password', 'token')
# The View
class RegisterUserView(CreateAPIView):
model = User
serializer_class = UserSerializer
permission_classes = (permissions.AllowAny, )
# And the URL pattern
urlpatterns = [
path(r'user/register/', RegisterUserView.as_view()),
]
So the user can create a new account by posting at least a username and password to user/register, and in the response he'll get the full user object (with first name, last name, email and the auth token). This works.
However, I am struggling with forcing lowercase usernames. For example when I add something like validated_data['username'] = validated_data['username'].lower() to the create function in the serializer, the server simply generates an error 500 "UNIQUE constraint failed: auth_user.username" when trying to create a user with the same username (but different case). That's of course not ideal, that error 500.
I've found some suggestions to add a custom user manager, something like this:
lass MyUserManager(BaseUserManager):
def get_by_natural_key(self, username):
return self.get(username__iexact=username)
But that didn't do anything either, after hooking it up via a custom user class and adding to the settings file. I could still create users with capitals in their username.
What's the simplest solution to get this to simply work?
If you're using a custom user model and want this enforced throughout Django, you can create a custom validator. For example, create a validators.py as a sibling to the models.py which contains your custom user model:
from django.core import validators
from django.utils.deconstruct import deconstructible
from django.utils.translation import gettext_lazy as _
#deconstructible
class MyUsernameValidator(validators.RegexValidator):
"""
Validator for usernames.
- Only ASCII lowercase letters, numbers and underscore are supported.
- Must start with a letter.
"""
regex = r'^[a-z][a-z0-9_]+$'
message = _(
'Enter a valid username. This value may contain only lowercase ASCII letters, '
'numbers, and underscores. Must start with a letter.'
)
flags = 0
Then in your custom user model, include:
from .validators import MyUsernameValidator
...
class User(AbstractBaseUser, PermissionsMixin):
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = [
'first_name', 'last_name', 'email',
]
username = models.CharField(
max_length=16,
unique=True,
help_text=_(
'Required. 16 characters or fewer. Lowercase letters, digits _ only; must start with a letter.'
),
validators=[MyUsernameValidator()],
error_messages={
'unique': _("A user with that username already exists."),
},
)
There are some slightly simpler methods, but this will enforce it through Django, as long as you stick to the ORM. Good luck!
Probably the reason you are getting "UNIQUE constraint failed: auth_user.username" error is because username validation is run on unprocessed version of the username (i.e with uppercase letters), but when inserting you are trying to create a user with username converted to lowercase. To exemplify, let's say you have a user in the db with the following username:
my_username
Then you are trying to create a user with the following username:
My_Username
This passes validation because there is no user in the db with username "My_Username", but while creating, you are trying to create a user with username "my_username", which exists in the database, so you are getting an IntegrityError.
There are many ways to solve this problem, you can modify the username before passing it to the serializer, so in the serializer context username will always be lowercase. You can also use a custom ModelManager as you tried already, but you'd need to convert the uesrname to lowercase in the manager before saving. Either way, you need to validate your data with the correct version of the username, to do this you can add a validation to your serializer like this:
class UserSerializer(serializers.ModelSerializer):
...
validate_username(self, value):
try:
get_user_model().objects.get(username=value.lower())
except ObjectDoesNotExist:
# Good to go
pass
else:
# Username in use
raise serializers.ValidationError(...)
return value
Sorry if I have overlooked something in docs of django-forms-utils by Carl Meyer. We're using it for the getting fieldset in non-admin pages in django. Here is my code in forms.py:
class MentorApplicationForm(ApplicationForm):
email = forms.EmailField(label='Email', error_messages={'email_exists': 'This email id has already been used. Please use a different email id to submit a new application. Or write to applications#mentortogether.org for clarifications.'},)
dob = forms.DateField(label=u'Date of Birth',
help_text=u'Format dd/mm/yyyy',
required=True,
input_formats=("%d/%m/%Y",))
class Meta:
model = MentorApplication
fieldsets = [('Personal Information', {'fields': ['email','dob'],})]
What I want is something like this:
class MentorApplicationForm(ApplicationForm):
email = forms.EmailField(label='Email', error_messages={'email_exists': 'This email id has already been used. Please use a different email id to submit a new application. Or write to applications#mentortogether.org for clarifications.'},)
dob = forms.DateField(label=u'Date of Birth',
help_text=u'Format dd/mm/yyyy',
required=True,
input_formats=("%d/%m/%Y",))
class Meta:
model = MentorApplication
fieldsets = [('Personal Information', {'fields': ['first_name', 'last_name', 'email','dob',],})]
where I have defined 'first_name' and 'last_name' in the MentorApplication models and I don't want to redefine them in forms.py. Here is the doc where it says that it extends from ModelForm and that's where I expected that it would take the fields from the MentorApplication model(mentioned in Meta). Can some one help me out finding the reason and also suggest me a workaround(I am quite resistant on redefining it in form, since I'll have to repeat myself). Thanks in advance.
I was extending ApplicationForm (super of MentorApplicationForm) from the BetterForm. I should have instead extended it from the 'BetterModelForm'.
Django 1.2 allows usernames to take the form of an email address.
Changed in Django 1.2: Usernames may
now contain #, +, . and - characters
I know that's a much-requested feature, but what if you don't want the new behavior? It makes for messy usernames in profile URLs and seems to break django-registration (if a user registers an account with an email-style username, the link in the django-registration activation email returns 404).
Does anyone have a recipe for restoring the old behavior and disabling email-style usernames?
There is no straightforward way to revert back to old behavior.
The easiest way to handle this would be to enforce client and server side validation of usernames according to your demands. django-registration is not an actively developed component, I wouldn't count on anything coming from that direction. Just add some extra validation on your side.
To quote Jacob on this matter:
[...] another common request is to allow the
use of email addresses as usernames.
Custom registration/signup forms can
deal with further restrictions.
django-registration actually wasn't the problem here. The problem was that I had subclassed its RegistrationForm, redefining the username field with new help_text. In so doing, I had prevented it from using its own regex field. To fix this, I had to pull a few bits and pieces from RegistrationForm into my EnhancedRegistrationForm subclass.
Note the regex line, which mirrors the old-style username character restrictions (which is what I want).
from registration.forms import RegistrationForm
# Carry these over from RegistrationForm - needed in the form definition below
attrs_dict = {'class': 'required'}
from django.utils.translation import ugettext_lazy as _
class EnhancedRegistrationForm(RegistrationForm):
first_name = forms.CharField(label='first name', max_length=30, required=True)
last_name = forms.CharField(label='last name', max_length=30, required=True)
username = forms.RegexField(regex=r'^\w+$',
max_length=30,
widget=forms.TextInput(attrs=attrs_dict),
help_text='Email addresses cannot be used as usernames.',
required=True,
label=_("Username"),
error_messages={'invalid':"You cannot use an email address as a username, sorry."})
class Meta:
fields = ('first_name','last_name','username','email','password1','password2')
def save(self, *args, **kwargs):
"""
Overriding save, so call the parent form save and return the new_user
object.
"""
new_user = super(EnhancedRegistrationForm, self).save(*args, **kwargs)
new_user.first_name = self.cleaned_data['first_name']
new_user.last_name = self.cleaned_data['last_name']
new_user.save()
return new_user