I am using the authtools plugin to manage the user and its profile in django, but when I create the user, it does not create its profile, I have to go to the admin part of the site and create it manually.
I separated the applications into account and profile.
This is the profiles model:
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
primary_key=True)
slug = models.UUIDField(default=uuid.uuid4, blank=True, editable=False)
email_verified = models.BooleanField("Email verified", default=True)
This is the signal.py, that is inside of the profiles application:
#receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_profile_handler(sender, instance, created, **kwargs):
if not created:
return
profile = models.Profile(user=instance)
profile.save()
logger.info('New user profile for {} created'.format(instance))
This is the admin.py of the account app:
class UserProfileInline(admin.StackedInline):
model = Profile
class NewUserAdmin(NamedUserAdmin):
inlines = [UserProfileInline]
list_display = ('is_active', 'email', 'name', 'permalink',
'is_superuser', 'is_staff',)
# 'View on site' didn't work since the original User model needs to
# have get_absolute_url defined. So showing on the list display
# was a workaround.
def permalink(self, obj):
url = reverse("profiles:show",
kwargs={"slug": obj.profile.slug})
# Unicode hex b6 is the Pilcrow sign
return format_html('{}'.format(url, '\xb6'))
admin.site.unregister(User)
admin.site.register(User, NewUserAdmin)
admin.site.register(Profile)
When I signup a user, both the user and the profile objects are created, only they are not linked. Why is that?
Thank you
Use this below your profile model in models.py. I hope you are generating slug by another slugify signal.
def user_created_receiver(sender, instance, created, *args, **kwargs):
if created:
Profile.objects.get_or_create(user = instance)
post_save.connect(user_created_receiver, sender = User)
Related
I am using django-allauth for taking car of accounts, login, logout, signup, but I need that on create the user must create a profile and I am using the model UserProfile as it can be seen on the code. The problem is that when I created the custom signup form, now it creates a user with [username, email, first_name, last_name, password] but it does not create a UserProfile. And I have three questions:
How to create a User and a UserProfile on signup?
How can add styling to the forms that come with django-allauth, i.e. at /accounts /login/
How can I modify so that when the user logs in, it will redirect him to /profiles/ instead of /accounts/profiles or is it better in terms of REST principles to have it /accounts/profiles/ if yes, then is it possible to modify the profiles app so that it can use django-allauth views?
My custom signup form:
# django_project/profiles/forms.py
from django import forms
from allauth.account.forms import SignupForm
class CustomSignupForm(SignupForm):
first_name = forms.CharField(max_length=30, label='First Name')
last_name = forms.CharField(max_length=30, label='Last Name')
bio = forms.CharField(max_length=255, label='Bio')
def save(self, request):
user = super(CustomSignupForm, self).save(request)
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.bio = self.cleaned_data['bio']
user.save()
return user
Settings:
# django_project/django_project/settings.py
ACCOUNT_FORMS = {
'signup': 'profiles.forms.CustomSignupForm',
}
And main url patterns:
# django_project/django_project/urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('profiles/', include('profiles.urls')),
path('accounts/', include('allauth.urls')),
]
Url patterns in profiles app:
# django_project/profiles/urls.py
app_name = 'profiles'
urlpatterns = [
path('<str:user>/', ProfileView.as_view(), name='profile-detail'),
]
And this is my ProfileView:
class ProfileView(LoginRequiredMixin, View):
def get(self, request, user, *args, **kwargs):
profile = UserProfile.objects.get(user=user)
my_user = profile.user
context = {
'user': my_user,
'profile': profile,
}
return render(request, 'profile/profile.html', context)
I have a different user profile than the User model that comes with django user model:
User = settings.AUTH_USER_MODEL
class UserProfile(models.Model):
user = models.OneToOneField(User, primary_key=True, verbose_name='user',
related_name='profile', on_delete=models.CASCADE)
first_name = models.CharField(max_length=30, blank=True, null=True)
last_name = models.CharField(max_length=30, blank=True, null=True)
email = models.CharField(max_length=30, blank=True, null=True)
bio = models.TextField(max_length=500, blank=True, null=True)
The Signals of user creation:
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
How to create a User and a UserProfile on signup?
You can create a UserProfile at the same time you save the CustomSignupForm
def save(self, request):
user = super(CustomSignupForm, self).save(request)
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.bio = self.cleaned_data['bio']
user.save()
# Create your user profile
UserProfile.objects.create(user=user, first_name=self.cleaned_data['first_name'], last_name=self.cleaned_data['last_name'], email=self.cleaned_data['email'], bio=self.cleaned_data['bio'])
Another elegant way is to use Django signals to perform some actions after that an event occur like user creation.
signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import UserProfile
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
first_name = instance.first_name
last_name = instance.last_name
email = instance.email
# The bio field is not set because the User instance has not bio attribute by default.
# But you can still update this attribute with the profile detail form.
UserProfile.objects.create(user=instance, first_name=first_name, last_name=last_name, email=email)
If you want to update the profile each time an user is updated, then remove the if created in the signal body.
apps.py
class AppNameConfig(AppConfig):
# some code here
# import your signal in the ready function
def ready(self):
import app_name.signals
How to create a User and a UserProfile on signup?
class CustomSignupForm(SignupForm):
first_name = forms.CharField(max_length=30, label='First Name')
last_name = forms.CharField(max_length=30, label='Last Name')
bio = forms.CharField(max_length=255, label='Bio')
def save(self, request):
# create user the create profile
user = super(CustomSignupForm, self).save(request)
### now save your profile
profile = UserProfile.objects.get_or_create(user=user)
profile.first_name = self.cleaned_data['first_name']
profile.last_name = self.cleaned_data['last_name']
profile.bio = self.cleaned_data['bio']
profile.save()
return user
How can add styling to the forms that come with django-allauth, i.e. at
make a new directory in your templates call it /account/login.html and render your form there and add styles as follow
this can be done in many ways
using https://pypi.org/project/django-bootstrap4/
https://pypi.org/project/django-widget-tweaks/
manually render fields https://simpleisbetterthancomplex.com/article/2017/08/19/how-to-render-django-form-manually.html
How can I modify so that when the user logs in, it will redirect him to /profiles/ instead of /accounts/profiles or is it better in terms of REST principles to have it /accounts/profiles/ if yes, then is it possible to modify the profiles app so that it can use django-allauth views?
go to your setting files and add the following
LOGIN_REDIRECT_URL = "/profiles"
you can check more settings here
https://django-allauth.readthedocs.io/en/latest/configuration.html
I like to bulk import users from xlsx or csv to my original User model. I don't use AbstractUser or AbstractBaseUser and I don't like to because my site is working and I don't want to abuse my schema.
I am using the original User model where I add the users (no user registration allowed) and I store the extra fields in my Profile model with a OneToOne relation.
I'm not so experienced so I tried to use code snippets that I found but I am still not able to achieve my goal. If I try to use import-export module in my admin panel it works with other models but not with the User model. Tablib or other solutions would be also interesting to me.
models.py
(I'm using the original User model that sends a signal to Profile model when a user is created)
class Profile(models.Model):
def __str__(self):
return str(self.user)
user = models.OneToOneField(User, null=True, on_delete=models.CASCADE)
date = models.DateField(auto_now_add=True, auto_now=False, blank=True)
projekt = models.ForeignKey(Projekt, on_delete=models.CASCADE, default=1)
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
forms.py
class RegisterForm(UserCreationForm):
email = forms.EmailField()
class Meta:
model = User
fields = ['username', 'email', 'last_name', 'first_name', 'password1', 'password2']
admin.py
from django.contrib.auth.models import User
from import_export.admin import ImportExportModelAdmin
#admin.register(User)
class UserAdmin(ImportExportModelAdmin):
pass
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
I got this error message:
django.contrib.admin.sites.AlreadyRegistered: The model User is already registered with 'auth.UserAdmin'.
I understand that my User model is in use but what could I do?
i wanted to add custom field with django-allauth SingupForm and adding new field like phone number. i already managed to add this field in Postgresql on my own(without migrations,but by my hands).
this is my postgresql screen
In my signup page i have these fields already but i can't managed to add "phone" to my database, i really want to make it! please someone help me.
forms.py
from allauth.account.forms import SignupForm
from django import forms
class CustomSignupForm(SignupForm):
first_name = forms.CharField(max_length=30, label='Voornaam')
last_name = forms.CharField(max_length=30, label='Achternaam')
phone = forms.CharField(max_length=30, label='phone')
def __init__(self, *args, **kwargs):
super(CustomSignupForm, self).__init__(*args, **kwargs)
self.fields['first_name'] = forms.CharField(required=True)
self.fields['last_name'] = forms.CharField(required=True)
self.fields['phone'] = forms.CharField(required=True)
def save(self, request):
user = super(CustomSignupForm, self).save(request)
user.phone = self.cleaned_data.get('phone')
user.save()
return user
def signup(self,request,user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.save()
return user
settings.py
ACCOUNT_FORMS = {'signup': 'registration.forms.CustomSignupForm'}
What you need is a profile model which is attached to a user so that you can add extra fields for information you might want.
If you're starting a project from the very beginning you can also consider a custom user model so that all data is on one object.
When I do this, I create an accounts app which I put my overrides in for allauth and my model starts something like this (modified to add the receiver function which I don't have at the moment because I'm not using Profile objects);
from django.contrib.auth import get_user_model
from django.db import models
from django.utils.translation import ugettext_lazy as _
User = get_user_model()
class Profile(models.Model):
"""
Profile model
"""
class Meta:
"""
Metadata
"""
app_label = 'accounts'
verbose_name = _('User profile')
verbose_name_plural = _('User profiles')
user = models.OneToOneField(
verbose_name=_('User'),
to=User,
related_name='profile',
on_delete=models.CASCADE
)
# Add your fields here like `phone`
def __str__(self):
"""
String representation
"""
return f'User Profile for: {self.user}'
#receiver(post_save, sender=User)
def create_profile(sender, instance, **kwargs):
"""
Setup a profile as a user is created
"""
Profile.objects.create(user=instance) # Using `create` also saves the object
Your signup form then does something like
def save(self, request):
user = super(CustomSignupForm, self).save(request)
user.profile.phone = self.cleaned_data.get('phone')
user.profile.save()
return user
If you've already got users you'll also need a migration which creates profiles for them;
# Generated by Django 2.2.12 on 2020-05-01 22:03
from django.db import migrations
def create_profiles(apps, schema_editor):
User = apps.get_model('authentication', 'User') # this should match the User model you are using
Profile = apps.get_model('accounts', 'Profile')
for user in User.objects.all():
profile = Profile.objects.create(user=user)
profile.save()
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
]
operations = [
migrations.RunPython(create_profiles, migrations.RunPython.noop)
]
i'm using Django 1.11 and the Django Rest Framework in order to create an API, where a user can create and update an employee which is related to the django user model.
The stripped down code i've got looks like this:
I've got this model:
class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='employee')
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
these two viewsets:
class UserViewSet(viewsets.ModelViewSet):
serializer_class = serializers.UserSerializer
permission_classes = [permissions.IsAuthenticated]
and
class EmployeeViewSet(viewsets.ModelViewSet):
serializer_class = serializers.EmployeeSerializer
permission_classes = [permissions.IsAuthenticated]
and these two serializers:
class UserSerializer(serializers.HyperlinkedModelSerializer)
class Meta
models = User
fields = ('url', 'id', 'username', 'email', 'is_staff', 'first_name', 'last_name', 'password')
read_only_field = ('id',)
def validate(self, data)
# ... Check if data is valid and if so...
return data
and
class EmplyoeeSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer()
class Meta:
model = Employee
field = ('url', 'uuid', 'user')
read_only_field = ('uuid')
def validate(self, data):
return data
def create(self, validated_data):
user = User(**validated_data['user'])
user.save()
employee = Employee(user=user)
employee.save()
return employee
def update(self, employee, user):
employee.user.username = user.username
employee.user.email = user.email
employee.user.first_name = user.first_name
employee.user.last_name = user.last_name
employee.user.is_staff = user.is_staff
# ... Check if password has changed, and if so...
employee.user.set_password(user.password)
employee.user.save()
employee.save()
return employee
also i've got these two routers in my urls.py
router = routers.DefaultRouter()
router.register(r'api/user', views.UserViewSet, base_name='user')
router.register(r'api/employee', views.UserViewSet, base_name='employee')
Creating and updating an instance of user is no problem.
I can also create an employee which in return will create an user and then an employee assigend to that user.
I can even update the username of the employee and the user will get updated too,
but i can't update first_name, last_name, email, is_staff and password.
DRF keeps telling me that the username is already taken, but when i change the username and other information like first_name and last_name and then send a PUT request to the server, the employee and associated user instance are getting updated properly.
What am i missing?
Why can't i update the employees user instance like i can update the normal user instance when i'm at the user api endpoint? :/
Thanks in advance,
any help would be appreciated.
Finally i found out what i was missing.
The UserSerializers adds django.contrib.auth.validators.UnicodeUsernameValidator and UniqueValidator to the field username which gets checked every time i do a put request.
Adding validators = [] to the Meta Class of UserSerializer solved my problem.
I am using Django 1.10.*. This is my user profile model:
class student(models.Model):
user = models.OneToOneField(User, primary_key=True, on_delete=models.CASCADE)
state = models.CharField(max_length=21, null=True, blank=True, choices=in_states.STATE_CHOICES)
city = models.CharField(max_length=21, null=True, blank=True)
date_joined = models.DateTimeField(default=timezone.now)
educational_role = models.CharField(max_length=39, choices=EDUCATIONAL_ROLE)
institute = models.ForeignKey(educational_institute, null=True, blank=True)
language = models.CharField(max_length=8, choices=LANGUAGES)
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
student.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
When I created form class for student and use that to create a View class extending FormView class in my Views passing the form context to HTML template like this:
forms.py :
class RegistrationForm(forms.ModelForm):
class Meta:
model = student
fields = ['user', 'state', 'city', 'educational_role', 'institute', 'language']
views.py :
class Register(FormView):
template_name = 'searcher/register.html'
form_class = RegistrationForm
def get_context_data(self, **kwargs):
context = super(Register, self).get_context_data(**kwargs)
context['applink'] = applink
context['forumlink'] = forumlink
return context
So how do I need to modify this so that the registration form asks for username, firstname, email of User model and also the fields added by student model and having option for creating a new educational_institute if it doesn't exist already?
You could use multi-table inheritance django.contrib.auth.models.User. Would look like this:
from django.contrib.auth.models import User
class Student(User):
state = models.CharField(...)
...
This way all Django features like ModelForm or UpdateView should function without any additional fiddling. In behind, Django will just create exactly the same OneToOneField for you.
And if all of your users are students you could also replace the auth user model entirely.
If you don't want to do any of those, you will need to add additional fields into your form, specify them if you form Meta, take care of setting their default values (in __init__ in example), cleaning these fields and saving the model behind OneToOneField all by yourself.