Adding an extra field in a ModelSerializer is not working - django

serializers.py:
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
import models
class ExtensibleModelSerializerOptions(serializers.Serializer):
def __init__(self, meta):
super(ExtensibleModelSerializerOptions, self).__init__(meta)
self.model = getattr(meta, 'model', None)
self.read_only_fields = getattr(meta, 'read_only_fields', ())
self.non_native_fields = getattr(meta, 'non_native_fields',())
class ExtensibleModelSerializer(serializers.ModelSerializerOptions):
_options_class = ExtensibleModelSerializerOptions
def restore_object(self, attrs, instance=None):
for field in self.opts.non_native_fields:
attrs.pop(field)
return super(ExtensibleModelSerializer, self).restore_object(attrs, instance)
def to_native(self, obj):
ret = self._dict_class()
ret.fields = {}
for field_name, field in self.fields.items():
# --- BEGIN EDIT --- #
if field_name in self.opts.non_native_fields:
continue
# --- END --- #
field.initialize(parent=self, field_name=field_name)
key = self.get_field_key(field_name)
value = field.field_to_native(obj, field_name)
ret[key] = value
ret.fields[key] = field
return ret
class OptiUserSerializer(ExtensibleModelSerializer):
password_confirmation = serializers.CharField(max_length=models.OptiUser._meta.get_field('password').max_length)
company_id = serializers.PrimaryKeyRelatedField(queryset=models.Company.objects.all(), many=False)
password = serializers.CharField(
style={'input_type': 'password'}
)
def validate_password_confirmation(self, attrs, source):
password_confirmation = attrs[source]
password = attrs['password']
if password_confirmation != password:
raise serializers.ValidationError(_('Password confirmation mismatch'))
return attrs
class Meta:
model = models.OptiUser
fields = ('id', 'username', 'company_id', 'first_name', 'last_name', 'is_active', 'is_admin', 'password', 'password_confirmation',)
non_native_fields = ('password_confirmation',)
I'm having a customized User model for my app. I'm creating web a web service using DRF. I'm trying to make a user registration serializer as specified above. Everything is working fine except when I add 'password_confirmation' field it raises exception "model_name" object has no attribute password_confirmation". I found a code snippet as above which I'm trying to use above. But unfortunately it doesn't seems to be much helpful.
My question is that, Is it possible to add such a field and perform some comparisons on it. Is there any elegant way to do it?
Please suggest a fix.
Thanks! in Advance.

I don't think you need ExtensibleModelSerializer. Set the field to write_only:
password_confirmation = serializers.CharField(write_only=True, ...)

Related

Cannot assign "": "" must be a '' instance

I am trying to make a registration key in the UserModel where the key field in the registration form is a foreign key to another model called RegistrationKey. I have made two posts about this topic earlier without any success, however has a few things changed in my code which makes those previous posts irrelevant. In the form field, the field for the key is a CharField as I can not display the keys for the users due to safety.
These are the two previous posts:
Save user input which is a string as object in db ,
Textinput with ModelChoiceField
These are my two models.
class RegistrationKey(models.Model):
key = models.CharField(max_length=30)
def __str__(self):
return self.key
class User(AbstractUser):
key = models.ForeignKey(RegistrationKey, on_delete=models.CASCADE, null=True, blank=True)
Since my latest posts have I created a class based view, which looks like this:
class RegisterPage(CreateView):
form_class = MyUserCreationForm
def form_valid(self, form):
key = form.cleaned_data['key']
try:
keyobject = RegistrationKey.objects.get(key=key)
form.instance.key = keyobject
return super().form_valid(form)
except RegistrationKey.DoesNotExist:
form.add_error('key', 'error')
return super().form_invalid(form)
When I try and pass in the value Admin which is an object in the RegistrationKey model I get the following error:
'Cannot assign "'Admin'": "User.key" must be a "RegistrationKey" instance.'
I don't know how to solve this, how can this string that the user inputs be assigned to the db?
Edit
Here are my form
class MyUserCreationForm(UserCreationForm):
key = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control', 'placeholder':'Key'}), label='')
email = forms.EmailField(widget=forms.EmailInput(attrs={'class':'form-control', 'placeholder':'Email'}), label='')
class Meta:
model = User
fields = ('key', 'email', 'password1', 'password2')
def __init__(self, *args, **kwargs):
super(MyUserCreationForm, self).__init__(*args, **kwargs)
self.fields['password1'].widget.attrs['class'] = 'form-control'
self.fields['password1'].widget.attrs['placeholder'] = 'Password'
self.fields['password1'].label=''
self.fields['password2'].widget.attrs['class'] = 'form-control'
self.fields['password2'].widget.attrs['placeholder'] = 'Confirm Password'
self.fields['password2'].label=''
for fieldname in ['password1', 'password2']:
self.fields[fieldname].help_text = None
You better move the logic to obtain the item to the form, where it belongs. So with:
from django.core.exceptions import ValidationError
class MyUserCreationForm(UserCreationForm):
# …
def clean_key(self):
key = self.cleaned_data['key']
try:
return RegistrationKey.objects.get(key=key)
except RegistrationKey.DoesNotExist:
raise ValidationError('The key is not valid.')
That should be sufficient. You should not override the .form_valid(…) method.

How to add extra field to django serializer?

I am trying to add an additional field to my serializer but I am getting the following error:-
"The field 'provider' was declared on serializer CreateUserSerializer, but has not been included in the 'fields' option."
Here is my serializer.py:-
class CreateUserSerializer(serializers.ModelSerializer):
email = serializers.EmailField()
username = serializers.CharField()
company = serializers.CharField()
provider = serializers.CharField()
password = serializers.CharField(write_only=True)
company_detail = serializers.SerializerMethodField()
branch_detail = serializers.SerializerMethodField()
def get_company_detail(self):
return {}
def get_branch_detail(self):
return {}
def create(self, validated_data):
try:
with transaction.atomic():
user = User.objects.create(**validated_data)
user_profile = UserProfileModel.objects.create(user=user)
user_profile.__dict__.update(**validated_data)
user_profile.save()
identity = FederatedIdentityModel.objects.create(user=user, oauth_provider=validated_data['provider'])
company = CompanyModel.objects.create(user=user, name=validated_data['company'])
branch = BranchModel.objects.create(user=user, name=validated_data['company'], company=company)
return user
except APIException:
raise APIException(
detail="Failed to register",
code=status.HTTP_500_INTERNAL_SERVER_ERROR
)
class Meta:
model = User
fields = ['first_name', 'last_name', 'password', 'email', 'username',
'company_detail', 'branch_detail']
I don't want to add the company and provider fields in the field option as it is not a part of user model. I just want to use them as writable fields so that I can create object for the two models.
How can I get rid of the following error?
I think you can't use that field without adding it to fields. What you can do is simply split model & extra fields into two lists and then define:
provider = serializers.CharField()
company = serializers.CharField()
...
class Meta:
model = User
model_fields = ['first_name', 'last_name', 'password', 'email', 'username',
'company_detail', 'branch_detail']
extra_fields = ['company', 'provider']
fields = model_fields + extra_fields
I found an alternative to solve this problem:-
class CreateUserSerializer(serializers.ModelSerializer):
email = serializers.EmailField()
company = serializers.CharField(write_only=True)
provider = serializers.ChoiceField(write_only=True, choices=OAUTH_PROVIDER_CHOICES)
password = serializers.CharField(write_only=True)
company_detail = serializers.SerializerMethodField()
branch_detail = serializers.SerializerMethodField()
# to get the details of company in response body
def get_company_detail(self, obj):
return {}
# to get the details of branch in response body
def get_branch_detail(self, obj):
return {}
def create(self, validated_data):
# pop additional fields from validated data to avoid error
company_name = validated_data.pop('company', None)
provider = validated_data.pop('provider', None)
try:
with transaction.atomic():
# create a user object
user = User.objects.create(**validated_data)
# create a user profile object
user_profile = UserProfileModel.objects.create(user=user)
user_profile.__dict__.update(**validated_data)
user_profile.save()
# create federated identity object using provider and email
identity = FederatedIdentityModel.objects.create(user=user, oauth_provider=provider,
email=validated_data['email'])
# create company object
company = CompanyModel.objects.create(user=user, name=company_name)
return user
except APIException:
raise APIException(
detail="Failed to register",
code=status.HTTP_500_INTERNAL_SERVER_ERROR
)
class Meta:
model = User
fields = ['first_name', 'last_name', 'password', 'email', 'username',
'company', 'provider', 'company_detail', 'branch_detail']
while creating user, I am popping the extra fields and using the popped value as the value while creating specific objects and adding the write_only=True in fields.
In that case, I can add the fields in my field_list without getting any error

Changing a form field's 'required' property with save_model in Django admin

I have tried all the different examples and methods I could find on Stack Overflow for this, and for whatever reason, can't get this to work properly.
So in my admin.py I have a UserForm and and UserAdmin. Based on the condition where a boolean is checked within the form, I want to change the 'required' attribute on a few different form fields to 'false' so I can effectively save. I've used a few different print statements to ascertain that the 'required' is in fact getting changed to false when the condition is met, however, when I try and save, it won't let me as the fields highlight and say they're still required.
It is almost like the save_model doesn't care how I edit the form, that the old form and its 'required' attributes are overriding my changes. Thanks for any help!
admin.py
class UserForm(forms.ModelForm):
state = forms.CharField(min_length=2, widget=forms.TextInput(attrs={'placeholder':'CA'}))
zipcode = forms.CharField(max_length=5, min_length=5,)
class Meta:
model = User
fields = '__all__'
class UserAdmin(admin.ModelAdmin):
model = Order
form = UserForm
def save_model(self, request, obj, form, change):
if obj.pickup_only == 1:
form.fields['address'].required = False
form.fields['city'].required = False
form.fields['state'].required = False
form.fields['zipcode'].required = False
return super(UserAdmin, self).save_model(request, obj, form, change)
revised code for UserForm:
def clean(self):
cleaned_data = super(UserForm, self).clean()
address = cleaned_data.get('address')
state = cleaned_data.get('state')
city = cleaned_data.get('city')
zipcode = cleaned_data.get('zipcode')
pickup_only = cleaned_data.get('pickup_only')
if pickup_only == True:
# I STUCK IN ADDRESS TO EMPHASIZE THE ERROR IN SCREENSHOT
self.fields_required(['first_name', 'last_name', 'dob', 'phone', 'email', 'address'])
else:
self.cleaned_data['address', 'city', 'state', 'zip code'] = ''
return self.cleaned_data
def fields_required(self, fields):
for field in fields:
print(field)
# RETURNS: first_name, last_name, dob, phone, email, address
if not self.cleaned_data.get(field, ''):
msg = forms.ValidationError("This field is required. Custom.")
self.add_error(field, msg)
When I save I get this:
You might have to tweak it a little bit to fit your needs, but you could try something like this in your clean method:
def clean(self):
cleaned_data = super(UserForm, self).clean()
state = cleaned_data.get('state')
zipcode = cleaned_data.get('zipcode')
pickup_only = cleaned_data.get('pickup_only')
if pickup_only == True:
self.fields_required(['any_fields_required',])
else:
self.cleaned_data['state', 'zip code'] = ''
return self.cleaned_data
def fields_required(self, fields):
for field in fields:
if not self.cleaned_data.get(field, ''):
msg = forms.ValidationError("This field is required.")
self.add_error(field, msg)
Also, as one of the commenters mentioned, you should not specify your model=Order in the UserAdmin. Your UserAdmin is for the User model. That isn't the proper way to add inlines. See below:
# You can also use (admin.TabularInline) depending on your needs.
class OrderInline(admin.StackedInline):
model = Order
list_display = ('order_fields',)
fieldsets = (
((''), {'fields': ('order_fields',)}),
)
class UserAdmin(admin.ModelAdmin):
model = User
inlines = [OrderInline,]
Add an init method to your form:
class UserForm(forms.ModelForm):
state = forms.CharField(min_length=2, widget=forms.TextInput(attrs={'placeholder':'CA'}))
zipcode = forms.CharField(max_length=5, min_length=5,)
class Meta:
model = User
fields = '__all__'
def __init__(self, *args, **kwargs):
super(UserForm, self).__init__(*args, **kwargs)
self.fields['address'].required = False
self.fields['city'].required = False
self.fields['state'].required = False
self.fields['zipcode'].required = False
def clean(self):
cleaned_data = super(UserForm, self).clean()
address = cleaned_data.get('address')
state = cleaned_data.get('state')
city = cleaned_data.get('city')
zipcode = cleaned_data.get('zipcode')
pickup_only = cleaned_data.get('pickup_only')
if pickup_only == True:
self.fields_required(['first_name', 'last_name', 'dob', 'phone', 'email', 'address'])
else:
self.cleaned_data['address', 'city', 'state', 'zip code'] = ''
return self.cleaned_data
def fields_required(self, fields):
for field in fields:
if not self.cleaned_data.get(field, ''):
msg = forms.ValidationError("This field is required. Custom.")
self.add_error(field, msg)
Try this. If it works then I don't think you have to override the save method in your UserAdmin. I would avoid that anyway unless absolutely necessary.

Overwrite maxlength/minlength of username by Django User model in the ModelForm

Try to overwrite User models by the following code, but somehow I cannot overwrite the max_length and min_length of username.
More specifically, when I check by python manage.py shell, I do overwrite them. But it seems has no effect on the html which was rendered(username maxlength is still 150).
Don't know which parts get wrong, please help.
from django import forms
from django.contrib.auth.models import User
class RegistrationForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(RegistrationForm, self).__init__(*args, **kwargs)
email = self.fields['email']
username = self.fields['username']
email.required = True
email.label_suffix = ' '
username.label_suffix = ' '
######### this is not work!!!###############
username.min_length = 6
username.max_length = 30
############################################
class Meta:
model = User
fields = ('username', 'email')
labels = {
'username': '帳號',
}
help_texts = {
'username': '',
}
Instead of modifying the form, you should modify/override the model.
I recommend using django-auth-tools for building your own custom user model. It supplies basic models, views and forms which can be easily extended.
If you are trying to override just the model form field, you could do something like this
class RegistrationForm(forms.ModelForm):
username = forms.CharField(required=True, min_length=6, max_length=30)
class Meta:
model = User
fields = ('username', 'email')
or
class RegistrationForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(RegistrationForm, self).__init__(*args, **kwargs)
self.fields['username'] = forms.CharField(required=True, min_length=6, max_length=30)
class Meta:
model = User
fields = ('username', 'email')
But I would recommend creating a Custom User Model inherited from AbstractBaseUser to override the username or email field as documented in https://docs.djangoproject.com/en/1.10/topics/auth/customizing/

Additional Information in ModelSerializer are not in used Model

I have a problem similar to this one: Additional (non model) fields in ModelSerializer
I want to create a object with a modelserializer like this:
class CreateUserSerializer(serializers.ModelSerializer):
user = serializers.CharField(source="username")
password = serializers.CharField()
password2 = serializers.WritableField()
...
class Meta:
model = User
fields = ('user', "password", "password2", ...)
Obviously, I am not interested in saving password2, it's just for the reason of comparison.
Ideally this should do the trick but for reasons, its not working as expected:
def restore_object(self, attrs, instance=None):
tags = attrs.pop('password2', None)
obj = super(CreateUserSerializer, self).restore_object(attrs, instance)
obj.tags = tags
logging.info(u"Object gets restored, field 'password2' gets removed, list with arguments for object: {0}".format(tags))
return obj
The Error I am getting is:
'User' object has no attribute 'password2'
...
/home/jan/projekte/alarmapp/eclipse_workspace/AlarmServer/AlarmApp/src/external/rest_framework/fields.py in get_component
55. val = getattr(obj, attr_name)
Any Idea why this isn't working?
Seems like this small Gist should be helpful:
from django.contrib.auth.models import User
from rest_framework import serializers
class CreateUserSerializer(serializers.ModelSerializer):
password2 = serializers.CharField()
def validate_password2(self, attrs, source):
password2 = attrs.pop(source)
if attrs['password'] != password2:
raise serializers.ValidationError('password mismatch')
return attrs
def to_native(self, obj):
self.fields.pop('password2')
return super(CreateUserSerializer, self).to_native(obj)
class Meta:
model = User