Trouble with signals when inheriting from an abstract class - python

My app has different sort of users (parents, teachers, students) and I created a Profile abstract class to inherit from. I'm trying to use a receiver function so that whenever a user is created, a profile is also created.
In the admin page, when I try to create a Teacher Object, I need to create a User object first, but when I create a user object, I run into an error "AttributeError: type object 'Profile' has no attribute 'objects'".
I suspect this is because Profile is an abstract class, so if a User is created, it causes an error to create a Profile? If so, what's the better way to do this? I'm just trying to manually create a Teacher object in the admin page.
#models.py
class Profile(TimeStampedModel):
user = models.OneToOneField(User, on_delete=models.CASCADE)
sex = models.CharField(max_length=1, choices=SEX)
user_type = models.CharField(max_length=100, choices=USER_TYPE)
class Meta:
abstract = True
class Teacher(Profile):
user_type = 'teacher'
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)

Related

OneToOne connection between User and Profile in Django

I am creating an app in django in which after user model is created, via the signal I create a profile:
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
#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()
But as far as I feel, this does not guarantees me that profile id always equals user's one
Is there any smart way to do this safer?
I think about creating a signal which will delete profile model after user one and opposite, but I still feel that not all cases are covered
When you are creating a separate model linked in such a way, you can add primary_key = True to your OneToOne field eg,
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
This will ensure you can use the same PK value for the User and the Profile.
It's not recommended to use the built-in django User model. It's better to create your own User model, that inherits from AbstractUser. Then override the save method:
from django.contrib.auth.models import AbstractUser
class MyUser(AbtractUser)
""" Add additional fields if you want """
def __str__(self):
return "something"
def save(self, *args, **kwargs):
Profile.objects.create(user=self)
super(MyUser, self).save(*args, **kwargs)
You can also override the delete() and other model methods, read the documentation about it here: https://docs.djangoproject.com/en/4.0/topics/db/models/#overriding-predefined-model-methods

How to get the user from a manytomanyfield in Django using Django singals and creating another object with the recived manytomany user field

I want to know is there any way where we can get the updated many to many field in one model using signals and post them to another model
class Post(models.Model):
name=models.CharField(max_length=24)
nc=models.ManyToManyField(settings.AUTH_USER_MODEL)
def __str__(self):
return self.name
# return self.name
m2m_changed.connect(receiver=like_post,sender=Post.nc.through)
I want to collect the updated record in the nc field of post model and using signals I want to create an object using function
here is the signal that connects to Post model
def like_post(sender, *args, **kwargs):
# if kwargs['action'] in ('post_update'):
if kwargs['action'] in ('post_add', 'post_remove','post_update'):
print(kwargs['instance'])
instance = kwargs['instance']
print(instance)
notify = Notify.objects.create(
recipient=instance,
creator=Post.objects.get(pk=50),
state='unread',
type=kwargs['action'],
)
else:
print('no instance')
in the recipient and the creator section I want to update those fields with an existing user object the creator is the person who updated the manytomanyfield and the recipient is the person who created that post
notify model:
class Notify(models.Model):
recipient = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='notify_recipient',on_delete=models.CASCADE)
creator = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='notify_sender',on_delete=models.CASCADE)
state = ('read', 'unread', 'unseen')
type = models.CharField(max_length=30, blank=True)
url = models.CharField(max_length=50, blank=True)
whenever I run this the instance just prints the post object name and fires this error
ValueError at /admin/posts/post/50/change/
Cannot assign "<Post: ok>": "Notify.recipient" must be a "User" instance.
You can see that your Notify class defines receipent as a ForeignKey element to the AUTH_USER_MODEL, but you are creating a Notify in your signal as:
notify = Notify.objects.create(
recipient=instance,
creator=Post.objects.get(pk=50),
state='unread',
type=kwargs['action'],
)
Here, the instance is an instance of Post rather than User, also you are using post instance in the creator field too. This is what causes the error.
To solve this error, you need to pass the user instance in those fields. For example, you can use something like:
notify = Notify.objects.create(
recipient=instance.nc, # find out how to pass the user of the post here
creator=Post.objects.get(pk=50), # replace by an user instance here instead of a post instance
state='unread',
type=kwargs['action'],
)
EDIT:
To make sure that the user instance is saved you need to override the save_model method of your ModelAdmin for post model as:
class PostAdmin(admin.ModelAdmin):
def save_related(self, request, form, formsets, change):
if not form.cleaned_data['nc']:
form.cleaned_data['nc'] = [request.user]
form.save_m2m()

Django "Manager isn't accessible via UserProfile instances"

Here is my views.py
if request.user.is_authenticated():
changepass = request.user.userprofile.objects.get(user=request.user)
if changepass.force_password_change == True:
changepass.force_password_change = False
changepass.save()
return HttpResponseRedirect('/login/register/')
elif changepass.force_password_change == False:
return HttpResponseRedirect('/main/')
This line changepass = request.user.userprofile.objects.get(user=request.user) is the problem according to Django. I am trying to access force_password_change from UserProfile.
As the title suggests, I am getting the error Manager isn't accessible via UserProfile instances.
Here is my models.py for reference.
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
force_password_change = models.BooleanField(default=True)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
You already have the UserProfile instance when you done request.user.userprofile,
at this point, you have a Instance of UserProfile. you can't use the object manager (.objects) from that.
you only need
changepass = request.user.userprofile
Another way to get the userprofile object, is doing
UserProfile.objects.get(user=request.user)
You can't access a Manager through a Model instance. In your case, request.user is an instance of the User class.
To access the manager, you need to use the UserProfile class directly.
Although, for what you're trying to accomplish, it's much easier to transverse the database through the request's user object:
changepass = request.user.userprofile
Try using just
changepass = UserProfile.objects.get(user=request.user)

Extending a model in Django

I am trying to create a custom user model. This model has to extend another model and also link to built-in django User. Currently I have:
class Entity(models.Model):
name = models.CharField(max_length=256, null=True, blank=True)
class Profile(Entity):
user = models.OneToOneField(User, on_delete=models.CASCADE)
#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()
But when I try to create a user I get an error: UNIQUE constraint failed: project_profile.entity_ptr_id.
Do I need to add another function to create entity first? Or should I structure my models in another way?
if you creating the Profile instance with User it already saved in the database if you use create method. you don't need to save seperatly.
ie the following signls is enough for saving and creating user profile instance
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
The OneToOne relation means that you can have only one profile per user.
Modify the code to
class Profile(Entity):
user = models.ForeignKey(User, on_delete=models.CASCADE)

Django How to show user's name in his profile in admin console

I attached a UserProfile class to User this way:
class UserProfile(models.Model):
url = models.URLField()
home_address = models.TextField()
user = models.ForeignKey(User, unique=True)
I have also implemented auto-creating of UserProfile if needed this way:
def user_post_save(sender, instance, signal, *args, **kwargs):
profile, new = UserProfile.objects.get_or_create(user=instance)
models.signals.post_save.connect(user_post_save, sender=User)
It works fine but I need a small feature - when I go to User Profiles in admin console, I see a list of UserProfiles for existing users. Their titles are shown as UserProfile object. I think it would be nice if I could set titles to corresponding user names, for example, john, kenny etc.
How can I do that?
Define a __unicode__ method for the UserProfile class:
def __unicode__(self):
return u"Profile for %s" % self.user.get_full_name()

Categories