I am writing website and i`d like to implement profile managment. Basic thing
would be to edit some of user details by themself, like first and last name
etc. Now, i had to extend User model to add my own stuff, and email address.
I am having troubles with displaying form. Example will describe better what i
would like achieve.
This is mine extended user model.
class UserExtended(models.Model):
user = models.ForeignKey(User, unique=True)
kod_pocztowy = models.CharField(max_length=6,blank=True)
email = models.EmailField()
This is how my form looks like.
class UserCreationFormExtended(UserCreationForm):
def __init__(self, *args, **kwargs):
super(UserCreationFormExtended, self).__init__(*args, **kwargs)
self.fields['email'].required = True
self.fields['first_name'].required = False
self.fields['last_name'].required = False
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email')
It works fine when registering, as i need allow users to put username and email but when it goes to editing profile it displays too many fields. I would not like them to be able to edit username and email. How could i disable fields in form?
Thanks for help.
You should create another form, that excludes the fields you don't want (or simply don't specify them in the fields list). Then pass the 2 different forms to the registration and edit-profile views.
Try removing 'username' and 'email' from fields in Meta:
class Meta:
model = User
fields = ('first_name', 'last_name')
What i did is i created new form and used it and it worked. It allows to edit fields from User model not only UserExtended. Thanks for help.
class UserProfileForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(UserProfileForm, self).__init__(*args, **kwargs)
try:
self.fields['first_name'].initial = self.instance.user.first_name
self.fields['last_name'].initial = self.instance.user.last_name
self.fields['email'].initial = self.instance.user.email
except models.User.DoesNotExist:
pass
email = forms.EmailField(label = "Główny adres email",
help_text="",
required=True)
first_name = forms.CharField(label = "Imię",
required=False)
last_name = forms.CharField(label = "Nazwisko",
required=False)
kod_pocztowy = forms.RegexField('\d{2}-\d{3}',
required = False,
label="Kod pocztowy",
error_messages={"invalid":'Poprawna wartość to np: 41-200'})
class Meta:
model = UserExtended
exclude = ('user')
def save(self, *args, **kwargs):
u = self.instance.user
u.email = self.cleaned_data['email']
u.first_name = self.cleaned_data['first_name']
u.last_name = self.cleaned_data['last_name']
u.kod_pocztowy = self.cleaned_data['kod_pocztowy']
u.save()
profile = super(UserProfileForm, self).save(*args, **kwargs)
return profile
Related
Currently, I'm having a problem when overriding a form field value on my (Django==4.0.3) django admin form.
The objective is :
I have a specific user table that I'm connecting to AWS Cognito. And when the admin creates a new user in django, the system must create a request to create a new Cognito user.
Once the Cognito user is created it generates a "sub" code, and then the sub should be saved in django
Code Follows
Model
class BuyerUser(BaseModel):
buyer = models.ForeignKey(
Buyer, on_delete=models.RESTRICT, related_name="%(class)s_buyer"
)
cognito_sub = models.CharField(max_length=50)
given_name = models.CharField(max_length=50)
family_name = models.CharField(max_length=50)
preferred_username = models.CharField(max_length=50)
email = models.EmailField(blank=False)
terms_conditions_accepted_datetime = models.DateTimeField(null=True, blank=True)
def __str__(self):
return self.preferred_username
admin
class BuyerUsers(admin.ModelAdmin):
list_display = ('id', 'buyer', 'given_name', 'family_name', 'preferred_username', 'available')
list_filter = ('buyer', 'available',)
list_display_links = ('id', 'preferred_username',)
search_fields = ('buyer__name', 'preferred_username', 'available')
list_per_page = 20
form = BuyerUserChangeForm
add_form = BuyerUserAddForm # It is not a native django field. I created this field and use it in get_form method.
def get_form(self, request, obj=None, **kwargs):
"""
Use special form during foo creation
"""
defaults = {}
if obj is None:
defaults['form'] = self.add_form
defaults.update(kwargs)
return super().get_form(request, obj, **defaults)
admin.site.register(BuyerUser, BuyerUsers)
and my forms
class BuyerUserAddForm(forms.ModelForm):
grupo = forms.CharField()
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=None,
empty_permitted=False, instance=None, use_required_attribute=None,
renderer=None):
super().__init__(data, files, auto_id, prefix, initial, error_class, label_suffix, empty_permitted, instance,
use_required_attribute, renderer)
self.cognito_sub = None
def save(self, commit=True):
grupo = self.cleaned_data.get('grupo', None)
self.given_name = self.cleaned_data.get('given_name', None)
self.family_name = self.cleaned_data.get('family_name', None)
self.preferred_username = self.cleaned_data.get('preferred_username', None)
self.email = self.cleaned_data.get('email', None)
cognito = CognitoDriver()
sub = cognito.parse_user(
cognito.create_user(self.preferred_username, self.email)["User"]
)["Sub"]
self.cognito_sub = sub
cognito.add_group(self.preferred_username, grupo)
return super(BuyerUserAddForm, self).save(commit=commit)
class Meta:
model = BuyerUser
# fields = '__all__'
exclude = ['terms_conditions_accepted_datetime']
class BuyerUserChangeForm(forms.ModelForm):
class Meta:
model = BuyerUser
fields = '__all__'elf.cognito_sub = sub
cognito.add_group(self.preferred_username, grupo)
return super(BuyerUserAddForm, self).save(commit=commit)
class Meta:
model = BuyerUser
# fields = '__all__'
exclude = ['terms_conditions_accepted_datetime']
class BuyerUserChangeForm(forms.ModelForm):
class Meta:
model = BuyerUser
fields = '__all__'
create
Change
This cognito sub field should have its value override after cognito-user is created. as it should be happening in the following code
cognito = CognitoDriver()
sub = cognito.parse_user(
cognito.create_user(self.preferred_username, self.email)["User"]
)["Sub"]
self.cognito_sub = sub
In fact, this cognito-user is being created and the sub is correct. the BIG PROBLEM is: this sub is not saved. It is getting only the value from the form.
I've tried to hide sub field using exclude = ['cognito_sub','terms_conditions_accepted_datetime']
but only happens to save a empty value.
You may ask why I use Forms instead of simply override model.Save() method
and the answer is: I need the grupo field, but this field must be persisted in DB. It only exists in Cognito.
You have to assign the value to form.instance instead of directly to the form itself.
class BuyerUserAddForm(forms.ModelForm):
grupo = forms.CharField()
# ...
def save(self, commit=True):
grupo = self.cleaned_data.get('grupo', None)
self.preferred_username = self.cleaned_data.get('preferred_username', None)
self.email = self.cleaned_data.get('email', None)
cognito = CognitoDriver()
sub = cognito.parse_user(
cognito.create_user(self.preferred_username, self.email)["User"])["Sub"]
self.instance.cognito_sub = sub
cognito.add_group(self.preferred_username, grupo)
return super(BuyerUserAddForm, self).save(commit=commit)
You might even want to disable the input field completly using the disabled attribute. https://docs.djangoproject.com/en/4.0/ref/forms/fields/#disabled
class BuyerUserAddForm(forms.ModelForm):
cognito_sub = forms.CharField(disabled=True)
# ...
I am a newbie at Django and I have come across this problem with my code.
I have a Custom User Model and an Account model which are related by many-to-many field.
During SignUp a user is asked to either create an Account or not ( join other account through a link ).
If the User creates an Account then he is the owner of the account and other Users can join the account.(Did not finish the code for ownership)
One User can be a part of several accounts at the same time.
Creation of Account(or not) and the User takes place in the Signup view.
I read up about the nested serializer in the documentation and i think this should create the two models instances.
How to create relationships in one view using nested serializers?
Other Approaches to solve the issue?
Models
class Account(models.Model):
AccountName = models.TextField(max_length=100, blank=False, null=False)
class User(AbstractBaseUser):
AccountName = models.ManyToManyField(Account)
CreateAccount = models.BooleanField(blank=False, null=False)
EmailId = models.EmailField(max_length=128, blank=False, null=False, unique=True)
is_admin = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
USERNAME_FIELD = 'EmailId'
REQUIRED_FIELDS = ['AccountName', 'CreateAccount',]
# Implemented the other req. functions
objects = MyAccountManager()
Serializers
class AccountCreationSerializer(ModelSerializer):
class Meta:
model = Account
fields = ['AccountName']
class SignUpSerializer(ModelSerializer):
AccountName = AccountCreationSerializer()
class Meta:
model = User
fields = ['EmailId', 'AccountName', 'CreateAccount', 'password']
extra_kwargs = {'password': {'write_only': True, 'required': True}}
def create(self, validated_data):
AccountName = validated_data.pop('AccountName')
if validated_data['CreateAccount']: #Create only when this is True
Account.objects.create(AccountName=AccountName, **AccountName)
userAcc = User.objects.create_user(**validated_data)
return userAcc
View
class SignUpView(APIView):
def post(request):
# to edit
signup_serializer = SignUpSerializer(data=request.data)
# rest of the view
The request
// Ignoring the quotes
EmailID: xyz#gmail.com
AccountName: TestAcc
CreateAccount: False
Password: ****
Error:
Direct assignment to the forward side of a many-to-many set is prohibited. Use AccountName.set() instead.
Create_user in Custom model
def create_user(self, EmailId, AccountName, CreateAccount, password):
if not EmailId:
raise ValueError("Users must have an email")
user = self.model(
EmailId=self.normalize_email(EmailId),
AccountName=AccountName,
CreateAccount=CreateAccount,
)
user.set_password(password)
user.save(using=self._db)
return user
I am pretty sure I am making some mistake regarding the manytomany field but haven't been able to figure out the solution. Any help would be of benefit to me. TIA!
You can not save value directly to many-to-many field. Database does not allow you to do so. It only allows you to add them for associating the relationship between the two tables ( i.e User, Account ). Replace your code segment for Serializer file with the following one.
class AccountCreationSerializer(ModelSerializer):
class Meta:
model = Account
fields = ['AccountName']
class SignUpSerializer(ModelSerializer):
AccountName = serializers.SerializerMethodField()
class Meta:
model = User
fields = ['EmailId', 'AccountName', 'CreateAccount', 'password']
extra_kwargs = {'password': {'write_only': True, 'required': True}}
def validate(self, attrs):
attrs = super(SignUpSerializer, self).validate(attrs=attrs)
attrs.update({"AccountName": self.initial_data.get("AccountName")})
return attrs
def create(self, validated_data):
AccountName = validated_data.pop('AccountName')
acc = Account.objects.create(AccountName=AccountName) if "CreateAccount" in validated_data and validated_data['CreateAccount'] else None
userAcc = User.objects.create_user(**validated_data)
if acc:
userAcc.AccountName.add(acc)
return userAcc
Finally, replace your SignUpView class in the following way:
class SignUpView(APIView):
serializer_class = SignUpSerializer
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
is_valid_serializer = serializer.is_valid(raise_exception=True)
if is_valid_serializer:
with transaction.atomic():
serializer.save()
# Rest of your code segments to finalize the response
UPDATE
There is a problem with your create_user method. You are here passing the many-to-many field reference (AccountName), which you shouldn't. As I mentioned earlier, you can not save directly many-to-many field. You just need to associate the relation between them. Omit that and it will work!!!
Follow this new definition for this method (create_user).
def create_user(self, EmailId, CreateAccount, password):
if not EmailId:
raise ValueError("Users must have an email")
user = self.model(EmailId=self.normalize_email(EmailId), CreateAccount=CreateAccount)
user.set_password(password)
user.save(using=self._db)
return user
We're required to have two separate forms for two different types of users. Call them Client and Provider. Client would be the parent, base user, while Provider is a sort of extension. At any point a Client could become a Provider as well, while still maintaining status and information as a Client. So a Provider has both permissions as a Client and as a Provider.
I'm new to Django. All we're trying to do is register either user type, but have a one to one relation between Provider and Client tables if a user registers as a Provider straight away.
The issue we're having is in the adapter, we think. A provider registers fine, but ends up in the users_user table with no entry in the generated users_provider table. Is it the way we're trying to save and relate these two entities in the database, or something else?
We're trying to utilize allauth for authentication and registration.
Our code:
models.py:
class User(AbstractUser):
name = models.CharField(_('Name of User'), blank=True, max_length=255)
def __str__(self):
return self.username
def get_absolute_url(self):
return reverse('users:detail', kwargs={'username': self.username})
SEX = (
("M","MALE"),
("F","FEMALE"),
)
birthdate = models.DateField(_('Birth Date'), default=django.utils.timezone.now, blank=False)
sex = models.CharField(_('Sex'), choices=SEX, max_length=1, default="M")
isProvider = models.BooleanField(_('Provider'), default=False)
#Using User, not models.Model
class Provider(User):
HAS_BUSINESS = (
('YES','YES'),
('NO','NO'),
)
#Resolving asociation 1:1 to User
#NOTE: AUTH_USER_MODEL = users.User in setting
owner = models.OneToOneField(settings.AUTH_USER_MODEL)
has_business = models.CharField(_('Do you have your own business?'),max_length=2, choices=HAS_BUSINESS, default='NO')
isProvider = True
our forms.py
class ProviderForm(SignupForm,ModelForm):
name = forms.CharField(label='Name', strip=True, max_length=50)
lastname = forms.CharField(label='Last Name', strip=True, max_length=50)
Provider.isProvider = True
class Meta:
model = Provider
fields = '__all__'
exclude = GENERAL_EXCLUSIONS + [
'owner',
]
class ClientForm(SignupForm,ModelForm):
name = forms.CharField(label='Name', strip=True, max_length=50)
lastname = forms.CharField(label='Last Name', strip=True, max_length=50)
class Meta:
model = User
fields = "__all__"
exclude = GENERAL_EXCLUSIONS
def is_active(self):
return False
def __init__(self, *args, **kwargs):
super(ClientForm, self).__init__(*args, **kwargs)
views.py:
class ProviderRegisterView(SignupView):
template_name = 'account/form_provider.html'
form_class = ProviderForm
redirect_field_name = 'next'
view_name = 'registerprovider'
success_url = None
def get_context_data(self, **kwargs):
ret = super(ProviderRegisterView, self).get_context_data(**kwargs)
ret.update(self.kwargs)
return ret
registerprovider = ProviderRegisterView.as_view()
#View para el formulario de registro de usuarios clientes
class ClientRegisterView(SignupView):
template_name = 'account/form_client.html'
form_class = ClientForm
redirect_field_name = 'next'
view_name = 'registerclient'
success_url = None
def get_context_data(self, **kwargs):
ret = super(ClienteRegisterView, self).get_context_data(**kwargs)
ret.update(self.kwargs)
return ret
registerclient = ClienteRegisterView.as_view()
finally, our adapter.py:
#Per allauth documentation, settings changed:
#ACCOUNT_ADAPTER = 'projectname.users.adapters.RegisterUserAdapter'
class RegisterUserAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=True):
data = form.cleaned_data
user.first_name = data['name']
user.last_name = data['lastname']
#Saving Client info
user.sex = data['sex']
user.birthdate = data['birthdate']
#Normal allauth saves
user.username = data['username']
user.email = data['email']
if user.isProvider:
p = Provider()
p.owner = user
p.has_business = data['has_business']
if 'password1' in data:
user.set_password(data['password1'])
else:
user.set_unusable_password()
self.populate_username(request, user)
if commit:
#Save user
user.save()
#If it's also a Provider, save the Provider
if user.isProvider:
p.save()
return user
Any help or tips would be greatly appreciated. If I left something out, please let me know. I'm not sure if the problem is in the model itself, the way we represent the form, or the adapter. The way it stands, it doesn't matter what form we use, it's always saved as the base User table (our Client) and the Provider table never gets information saved to it.
With Django's new custom user model, only one user model can be set as settings.AUTH_USER_MODEL. In your example, you can set this to your User model.
Then for the optional provider data, create a separate model that is referenced by OneToOneField from your User model.
class User(AbstractUser):
...
provider = models.OneToOneField(Provider, null=True)
class Provider(models.Model):
...
This is the easiest way to work with multiple user types in Django, given the AUTH_USER_MODEL constraint.
Also, it's best to only subclass abstract models, otherwise you get multitable inheritance which results in hidden implied JOINs, degrading performance.
Finally, you can create the Provider object in your custom form's form.is_valid() method and assign user.provider = provider.
I have the following models.
class User(models.Model):
...
class Group(models.Model):
name = models.CharField(max_length=255)
creater = models.ForeignKey(User)
users = models.ManyToManyField(User, through='GroupUser', through_fields=('group', 'user'))
class GroupUser(models.Model):
group = models.ForeignKey(Group)
user = models.ForeignKey(User)
date_joined = models.DateField(auto_now_add=True, editable=False)
and for admin.py, I defined these.
class GroupAdminForm(forms.ModelForm):
users = forms.ModelMultipleChoiceField(
queryset=User.objects.all(),
required=False,
widget=FilteredSelectMultiple(
is_stacked=False,
),
)
class Meta:
model = Group
def __init__(self, *args, **kwargs):
super(GroupAdminForm, self).__init__(*args, **kwargs)
if self.instance.id:
self.fields['users'].initial = self.instance.users.all()
# Version 1 save()
def save(self, commit=True):
group = super(GroupAdminForm, self).save(commit=False)
if group.id:
group.users = self.cleaned_data['users']
if commit:
group.save()
group.save_m2m()
return group
# Version 2 save()
def save(self, commit=True):
group = super(GroupAdminForm, self).save(commit=False)
if group.id:
for user in self.cleaned_data['users']:
u = GroupUser(group=group, user=user)
u.save()
if commit:
group.save()
return group
class GroupAdmin(models.ModelAdmin):
...
form = GroupAdminForm
filter_horizontal = ['users']
...
The problem is no matter I use version 1 and 2 save() function, it returns an error:
Cannot set values on a ManyToManyField which specifies an intermediary model.
I found some similar questions on web but I still cannot get the ideas of how the to override the save() function. Any suggestions?
I know it may works if I do not define the intermediary model, but can do it in such way but still have the way to get the date_join field for each records?
How can i prepopulate the sign up form with extra data in django-allauth after I've been connected with Facebook during registration?
In my settings I have the following
settings.py
SOCIALACCOUNT_AUTO_SIGNUP = False
Let's say I have a UserProfile model with some data related to the user.
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User)
gender = models.CharField(max_length=1)
If I use the following form, a non-registered user can connect with Facebook and receives a registration form to fill (an instance of UserSignupForm), where the first name and the last name are already prepopulated. How can I fill automagically the gender using the data collected from Facebook?
In other words, I would like to use the gender taken from facebook extra data as initial data for the sign up form.
settings.py
ACCOUNT_SIGNUP_FORM_CLASS = 'UserSignupForm'
forms.py
class UserSignupForm(forms.ModelForm):
first_name = forms.CharField(label=_('First name'), max_length=30)
last_name = forms.CharField(label=_('Last name'), max_length=30)
class Meta:
model = UserProfile
fields = ['first_name', 'last_name', 'gender']
def signup(self, request, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
self.instance.user = user
self.instance.user.save()
self.instance.save()
It seems to me that I should change the adapter.
adapter.py
class UserProfileSocialAccountAdapter(DefaultSocialAccountAdapter):
def populate_user(self, request, sociallogin, data):
user = super(UserProfileSocialAccountAdapter, self).populate_user(request, sociallogin, data)
# Take gender from social data
# The following line is wrong for many reasons
# (user is not saved in the database, userprofile does not exist)
# but should give the idea
# user.userprofile.gender = 'M'
return user
I'm using a solution that doesn't use the adapter, but overrides the initialization of the signup form.
forms.py
class UserSignupForm(forms.ModelForm):
first_name = forms.CharField(label=_('First name'), max_length=30)
last_name = forms.CharField(label=_('Last name'), max_length=30)
class Meta:
model = UserProfile
fields = ['first_name', 'last_name', 'gender']
def __init__(self, *args, **kwargs):
super(UserSignupForm, self).__init__(*args, **kwargs)
if hasattr(self, 'sociallogin'):
if 'gender' in self.sociallogin.account.extra_data:
if self.sociallogin.account.extra_data['gender'] == 'male':
self.initial['gender'] = 'M'
elif self.sociallogin.account.extra_data['gender'] == 'female':
self.initial['gender'] = 'F'