Django models.py OneToOneField --- help creating a relationship between two models - python

I am having trouble testing a relationship between two models (CustomUser and Profile) located in different apps. I'm hoping someone can identify where I am going wrong here:
Here is my profiles/models.py --- you can see my user field attempting to create a OneToOne with with my users/models.py:
from django.db import models
from core.models import TimeStampedModel
class Profile(TimeStampedModel):
user = models.OneToOneField('users.CustomUser', on_delete=models.CASCADE)
first_name = models.CharField(max_length=30, blank=True)
last_name = models.CharField(max_length=30, blank=True)
bio = models.TextField(blank=True)
image = models.URLField(blank=True)
def __str__(self):
return self.user.username
Here is my users/models.py:
class CustomUser(AbstractBaseUser, PermissionsMixin, TimeStampedModel):
username = models.CharField(db_index=True, max_length=255, unique=True)
email = models.EmailField(db_index=True, unique=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
is_provider = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
objects = CustomUserManager()
def __str__(self):
return self.email
#property
def token(self):
return self._generate_jwt_token()
def get_short_name(self):
return self.username
def _generate_jwt_token(self):
dt = datetime.now() + timedelta(days=60)
token = jwt.encode({
'id': self.pk,
'exp': int(dt.strftime('%s'))
}, settings.SECRET_KEY, algorithm='HS256')
return token.decode('utf-8')
So the idea is that when I create a new user, a profile is automatically created as well. To do this, I am using a post_save signal in my users app:
users/signals.py:
from django.db.models.signals import post_save
from django.dispatch import receiver
from conduit.apps.profiles.models import Profile
from .models import User
#receiver(post_save, sender=User)
def create_related_profile(sender, instance, created, *args, **kwargs):
if instance and created:
instance.profile = Profile.objects.create(user=instance)
And finally an update to my users/init.py file:
from django.apps import AppConfig
class UsersAppConfig(AppConfig):
name = 'django.users'
label = 'users'
verbose_name = 'Users'
def ready(self):
import users.signals
default_app_config = 'django.users.UsersAppConfig'
That last update is something I am relatively unfamiliar with. I suspect this is where my problem is located.
I am able to resister a new user via an api call with no problem, however, when I test to see if a Profile object exists for that new user, I am left with the following error:
python manage.py shell
from users.models import CustomUser
u = CustomerUser.objects.last()
u
<CustomUser:testuser#gmail.com> --- everything works to this point
u.profile --- this is where it breaks down
I'm left with this error in shell:
users.models.CustomUser.profile.RelatedObjectDoesNotExist: CustomUser has no profile.
Any help would be appreciated, thanks!

I think your error is in your signal method:
from django.db.models.signals import post_save
from django.dispatch import receiver
from conduit.apps.profiles.models import Profile
from .models import User # you have CustomUser but you are calling User
#receiver(post_save, sender=User)
def create_related_profile(sender, instance, created, *args, **kwargs):
if instance and created: # you should only have created because you want this happen only when it is created
instance.profile = Profile.objects.create(user=instance)
Also, I see no need updating the users init.py.

Related

Best way to send a mail in Django Model when a user's password has been changed?

I am new to python and django this year and am just struggling to figure out how to send a simple mail via send_mail to a user after their password has been updated?
I have managed this via Signals with pre_save, however I don't want to make the user wait until the mail has been sent (which I can't work around as far as I know). With post_save, the previous state cannot be queried.
What would be the best way here if I gave the following user model?
class User(AbstractBaseUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.EmailField(verbose_name="email address", max_length=255, unique=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
# Tells Django that the UserManager class defined above should manage
# objects of this type
objects = UserManager()
def __str__(self):
return self.email
class Meta:
db_table = "login"
I had set this up with a pre_save signal, but this is not a solution for me because of the delay:
#receiver(pre_save, sender=User)
def on_change(sender, instance: User, **kwargs):
if instance.id is None:
pass
else:
previous = User.objects.get(id=instance.id)
if previous.password != instance.password:
send_mail(
"Your password has changed",
"......",
"info#examplpe.com",
[previous.email],
fail_silently=False,
)
Thanks in advance
If you are using a custom model, you can probably walk through the call to set_password() to set a flag on an instance and then detect its presence in the signal.
Try this example:
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.db.models.signals import post_save
class User(AbstractBaseUser, PermissionsMixin):
...
def set_password(self, password):
super(User, self).set_password(password)
self._set_password = True
#classmethod
def user_changed(cls, sender, instance, **kwargs):
if getattr(instance, '_set_password', False):
# Send your mail
post_save.connect(User.user_changed, sender=User)
You can override your save method for the User model. Here is an example from the docs alongside a check for changed values from SO:
class User(AbstractBaseUser):
...
__original_password = None
def __init__(self, *args, **kwargs):
super(User, self).__init__(*args, **kwargs)
self.__password = self.password
def save(self, *args, **kwargs):
if self.password != self.__original_password:
notify_user_of_password_change()
super().save(*args, **kwargs) # Call the "real" save() method.

Django models: Access to a multilevel link between tables

I have a problem to access certain data in my models.
I have a User model giving its id to a Profile model which is giving its id to a ProfileA model.
And when I create a User it automatically creates a Profile.
Here is my user_model
from django.db import models
from django.contrib.auth.models import AbstractUser
from django_countries.fields import CountryField
from .model_custom_user_manager import CustomUserManager
class User(AbstractUser):
"""auth/login-related fields"""
is_a = models.BooleanField('a status', default=False)
is_e = models.BooleanField('e status', default=False)
# objects = CustomUserManager()
def __str__(self):
return "{} {}".format(self.first_name, self.last_name)
My profile_model:
from django.db import models
from django_countries.fields import CountryField
from django.contrib.auth import get_user_model
User = get_user_model()
from django.db.models.signals import post_save
from django.dispatch import receiver
class Profile(models.Model):
"""non-auth-related/cosmetic fields"""
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
birth_date = models.DateTimeField(auto_now=False, auto_now_add=False, null=True)
nationality = CountryField(null=True)
def __str__(self):
return f'{self.user.username} Profile'
"""receivers to add a Profile for newly created users"""
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
And what I'm trying to do is create a ProfileA when a Profile is created AND when a User has is_a==True.
Here is my profilea_model:
from django.db import models
from .model_profile import *
from django.db.models.signals import post_save
from django.dispatch import receiver
class ProfileA(models.Model):
profile = models.ForeignKey(Profile, null=True, on_delete=models.CASCADE, related_name='profile_a')
biography = models.CharField(max_length=200, null=True)
def __str__(self):
return "{}".format(self.profile)
#receiver(post_save, sender=Profile)
def create_profile_profile_a(sender, instance, created, **kwargs):
if created & Profile.user.is_a == True:
ProfileA.objects.create(profile=instance)
And when I try creating a User with Postman:
{
"username":"Pepe",
"password":"Yolo1234",
"first_name":"Pepe",
"last_name":"Pepito",
"email":"pepe#yolo.com",
"is_a":true,
"is_e":false
}
I get the error:
AttributeError at /users/
'ForwardOneToOneDescriptor' object has no attribute 'is_a'
I've tried a lot of things but nothing woks and using the DjangDocs I can't understand.
What am I doing wrong?
Thanks for your responses!
The instance argument of the create_profile_profile_a function is the instance of Profile which can be easily pointed to User and further get the target attribute is_a.
The receiver part in profilea_model should change to:
#receiver(post_save, sender=Profile)
def create_profile_profile_a(sender, instance, created, **kwargs):
if created & instance.user.is_a == True:
ProfileA.objects.create(profile=instance)

How to get django-all auth to pass data to custom user model?

I'm making a site in Django using django-allauth for authentication.
I've created a custom user class in accounts.models to add a custom field, FavouriteTeam.
The issue I have is that the form renders fine and submits formdata for fav_team fine (as inspected in Chrome dev tools) but the fav_team entry doesn't get stored to user.FavouriteTeam and I can't figure out why.
I can go into the Django shell, import my User class from accounts.models, query for a user, add a .FavouriteTeam, and save just fine. It's just that the form doesn't seem to save the data into the new User instance for some reason.
I'm guessing it's due to the way django-allauth interacts with custom user models but I can't figure it out for the life of me. I've seen some similar posts but none have a situation like this or have a solution that seems to work for me.
Any ideas?
accounts.models: -
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
FavouriteTeam = models.ForeignKey('predictor.Team', on_delete=models.CASCADE, blank=True, null=True)
def __str__(self):
return self.email
accounts.forms: -
from django import forms
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import User
from allauth.account.forms import SignupForm
from predictor.models import Team
from django.contrib.auth import get_user_model
class CustomSignupForm(SignupForm):
first_name = forms.CharField(max_length=30, label='First Name')
last_name = forms.CharField(max_length=30, label='Last Name')
fav_team = forms.ModelChoiceField(queryset=Team.objects.all(), empty_label=None, label='Favourite Team')
class Meta:
model = get_user_model()
def signup(self, request, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.FavouriteTeam = self.cleaned_data['fav_team']
user.save()
return user
predictor.models: -
class Team(models.Model):
ShortName = models.CharField(max_length=4, primary_key=True)
Town = models.CharField(max_length=20)
Nickname = models.CharField(max_length=20)
Conference = models.CharField(max_length=3, null=True, blank=True)
Division = models.CharField(max_length=5, null=True, blank=True)
ConfDiv = models.CharField(max_length=9, null=True, blank=True)
Logo = models.ImageField(default='football.png', upload_to='logos')
def __str__(self):
return('{} {}'.format(self.Town, self.Nickname))
def save(self, *args, **kwargs):
self.ConfDiv = str(self.Conference)+" "+str(self.Division)
super(Team, self).save(*args, **kwargs)
For anyone looking at this and suffering the same issue, I eventually found the solution to be as below.
Firstly, I had to create adpaters.py within my accounts app and fill in the below: -
from allauth.account.adapter import DefaultAccountAdapter
class AccountAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=False):
data = form.cleaned_data
user.email = data['email']
user.first_name = data['first_name']
user.last_name = data['last_name']
user.FavouriteTeam = data['fav_team']
if 'password1' in data:
user.set_password(data['password1'])
else:
user.set_unusable_password()
self.populate_username(request, user)
user.save()
return user
Then I had to referenced the new account adapter in my project's settings.py file as below: -
ACCOUNT_ADAPTER = "accounts.adapters.AccountAdapter"
Hope that helps someone in the future.

How to add extra fields to registration end point of rest-auth

I am using rest-auth registration api for user registration. I have some extra fields in the UserProfile model.
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
org_id = models.CharField(max_length=100, default='')
is_teacher = models.BooleanField(blank=True, default=False)
def __str__(self):
return self.user.username
def create_profile(sender, **kwargs):
if kwargs['created']:
user_profile = UserProfile.objects.create(user=kwargs['instance'])
post_save.connect(create_profile, sender=User)
The UserProfile model is shown above. How can I add these fields to rest-auth regestration api endpoint and save the data to database.
I found an answer for myself
The serializers can be written as
from rest_framework import serializers
from rest_auth.registration.serializers import RegisterSerializer
from .models import UserProfile
class RegistrationSerializer(RegisterSerializer):
first_name = serializers.CharField(required=False)
last_name = serializers.CharField(required=False)
personal_id = serializers.CharField(required=True)
def custom_signup(self, request, user):
user.first_name = self.validated_data.get('first_name', '')
user.last_name = self.validated_data.get('last_name', '')
user.userprofile.personal_id = self.validated_data.get(
'personal_id', '')
user.save(update_fields=['first_name', 'last_name'])
user.userprofile.save(update_fields=['org_id'])
I didnt add the is_teacher because its optional.
In views.py extend the RegisterView of the rest_auth.regeistration.views to pass this data and its done.
class RegistrationView(RegisterView):
serializer_class = RegistrationSerializer
And finally add a url and pass RegisterView.as_view().

Django 2.1.3/SQLite : UNIQUE constraint failed: users_profile.user_id || while trying to access my superuser account

i am working on a website that has a social appeal and i have to create an AbstractUser Model to store extra info about the users; while doing so, i ran into this error while trying to log into django admin page with my superuser account.
UNIQUE constraint failed: users_profile.user_id.
i have rebuilt the project 3 times and the issue still occurs.
here's my models/forms/signals/admin.py files
models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.conf import settings
from PIL import Image
# Create your models here.
class UserModel(AbstractUser):
bio = models.TextField(max_length=500, blank=True, null=True)
location = models.CharField(max_length=35, blank=True, null=True)
birthday = models.DateField(blank=True, null=True)
url = models.URLField(blank=True, null=True)
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True)
image = models.ImageField(default='default.png', upload_to="profile_pics")
def __str__(self):
return self.user.username
def save(self, *args, **kwargs):
super(Profile, self).save(*args, **kwargs)
path = self.image.path
img = Image.open(path)
if img.height > 500 and img.width > 500:
output_size = (500, 500)
img.thumbnail(output_size)
img.save(path)
forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import UserModel, Profile
class UserModelCreationForm(UserCreationForm):
email = forms.EmailField()
class Meta:
model = UserModel
fields = [
'username',
'password1',
'password2',
'email',
]
class UserModelChangeForm(UserChangeForm):
email = forms.EmailField()
class Meta:
model = UserModel
fields = [
'username',
'email',
]
class ProfileUpdateForm(forms.ModelForm):
model = Profile
fields = [
'image',
]
signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.conf import settings
from .models import Profile
#receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_user_profile(sender, instance, created, **kwargs):
Profile.objects.create(user=instance)
#receiver(post_save, sender=settings.AUTH_USER_MODEL)
def update_user_profile(sender, instance, **kwargs):
instance.profile.save()
admin.py
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin
from .models import UserModel, Profile
from .forms import UserModelCreationForm, UserModelChangeForm
# Register your models here.
#admin.register(UserModel)
class UserModelAdmin(UserAdmin):
add_form = UserModelCreationForm
form = UserModelChangeForm
model = UserModel
list_display = [
'username', 'email', 'first_name', 'last_name',
]
admin.site.register(Profile)
i have added AUTH_USER_MODEL = 'users.UserModel' to settings.py
i have added default_app_config = 'users.apps.UsersConfig' to users/__init__.py to automatically create profiles.
i have added def ready(self): import users.signals to apps.py
all i am trying to do is access my superuser account. i have created 7 different superusers (just to check) they all raise the same error.
I think the signals are redundant. Only one signal should be fine, it should be like this:
#receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)

Categories

Resources