Django not loading fixtures when there is post_save signal - django

I am testing a registration function in my Django app. When users register, a default user_type is designated to the user. I am using a post_save signal to process this.
I have a Profile model to be connected with the User and under the Profile model, is a field user_type which is from another model.
class UserType(models.Model):
name = models.CharField(max_length=50, blank=True)
def __str__(self):
return self.name
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
user_type = models.ForeignKey(UserType, on_delete=models.SET_NULL, null=True, blank=True)
To automatically assign a default user_type, I have a signal:
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
user_type = UserType.objects.get(pk=1)
Profile.objects.create(
user=instance,
user_type=user_type)
I made a fixture of my User, Profile, and UserType models and in my APITestCase, I placed the fixture.
class TestUserManagement(APITestCase):
fixtures = ['fixtures/initial']
When I run the test, the fixtures does not seem to load to the test database.
File "/usr/src/app/fixtures/signals.py", line 15, in create_user_profile
user_type = UserType.objects.get(pk=1)
...
{model}.UserType.DoesNotExist: Problem installing fixture
'/usr/src/app/fixtures/initial.json': UserType matching query does not exist.

Related

How to override "Cannot asign must be an instance" in Django?

I have two models:
class Message(models.Model):
username = models.CharField(max_length=255)
room = models.CharField(max_length=255)
content = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
class Notification(models.Model):
notification_author = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name="notifauthor")
notification_from = models.ForeignKey(Order, on_delete=models.CASCADE, related_name="notiffrom")
is_read = models.BooleanField(default=False)
signals:
#receiver(post_save, sender=Message)
def create_user_notification(sender, instance, created, **kwargs):
if created:
Notification.objects.create(
notification_author=instance.username,
notification_from=instance.room)
#receiver(post_save, sender=Message)
def save_user_notification(sender, instance, **kwargs):
instance.username.save()
I am trying to create signal for creating notification after message is created. But have error:
Cannot assign "20": "Notification.notification_author" must be a "Profile" instance.
How to override it (if possible) without changing CharField to FK in Message model?
Found smth about eval(), but it does not still work
The question is how to convert the string to Profile model without
using FK
notification_author=Profile.objects.get_or_create(username=instance.username)

How to refer to django foreign key?

I used to use a OneToOneField relation to the User model, but I had to switch to foreign key (because I want to store multiple dates for 1 user). And now I can't seem to figure out how to refer to my data inside my view.
view.py
def get_data(request, *args,**kwargs):
data = {
'weight': request.user.user_profile.weight,
'goal': request.user.user_profile.goal,
'date': request.user.user_profile.created_at,
}
return JsonResponse(data)
models.py
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from datetime import date
# Create your models here.
class Profile(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_profile')
weight = models.FloatField(max_length=20, blank=True, null=True)
height = models.FloatField(max_length=20, blank=True, null=True)
goal = models.FloatField(max_length=20, blank=True, null=True)
created_at = models.DateField(auto_now_add=True)
def __str__(self):
return self.user.username
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
I think you should keep OneToOne field. If you want multiple dates you can create ForeignKey for the dates.
If you still want ForeignKey Profile-User, you can try to filter the Profile model, to get the particular profile you need, by username, date etc.:
profile = Profile.objects.get(user=request.user, created_at=request.user.date_joined)
data = {
'weight': profile.weight,
'goal': profile.goal,
'date': profile.created_at,
}

Create a separate user type

I am working on an intranet web application which needs two types of users. Normal users that can be setup from django admin and specific type of users -
Employees.
I have the following model for Employee type user.
class Employee(models.Model):
emp_name = models.CharField(max_length=500)
slug = models.SlugField(unique=True, default='')
location = models.CharField(max_length=200)
email = models.EmailField()
experience = models.TextField(blank=True)
primary_skill = models.ManyToManyField(PrimarySkill)
secondary_skill = models.ManyToManyField(SecondarySkill)
I tried having a OneToOneField like this as per the official doc and
this article:
user = models.OneToOneField(User, blank=True, null=True, on_delete=models.CASCADE)
#receiver(post_save, sender=User)
def create_employee(sender, instance, created, **kwargs):
if created:
Employee.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_employee(sender, instance, **kwargs):
instance.employee.save()
I realized that this is the opposite of what I want. Every time a User
is created from the admin, there was an entry created in the
app_employee table.
What I want is this:
Every time an Employee is created, I need a User created.
An Employee can be created using a separate signup form, say emp_signup
How do I approach this scenario?
I have achieved this using a custom user based on AbstractUser inspired by this article.
class CustomUser(AbstractUser):
pass
class Employee(CustomUser):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
# other fields
In settings.py, I then add the following key:
AUTH_USER_MODEL = 'myapp.CustomUser'
And wherever I need to refer the User class, I use get_user_model(), which will substitute our custom user, in views and forms as follows:
views.py
from django.contrib.auth import get_user_model
def user_profile(request):
User = get_user_model()
user = get_object_or_404(User, username=request.user.username)
return render(request, 'user/user_profile.html', {
'site_user': user
})
forms.py
class SignUpForm(UserCreationForm):
class Meta:
model = get_user_model()
fields = ('username', 'email', 'password1', 'password2',)

Django: User Profile in 1.9

RELATED: get user profile in django
The above shows how to get user profile but i read that the accepted answer method is deprecated.
How would I create/get/use user profile in django 1.9?
models.py
from django.contrib.auth.models import User
from django.db.models.signals import post_save
class UserProfile(models.Model):
user = models.OneToOneField(User)
address = models.TextField()
......
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
The above code will create a UserProfile record whenever a new user is created on User table. Then you can access the profile details like,
address = request.user.profile.address
get_profile() method returned additional informations about User. Currently, these informations can be stored in Custom User Model or in a seperate model which is related to User Model. You can do that by simply adding one2one relation with User model to your custom User model, or by subclassing the AbstructUserBase model.
Subclassing User Model example:
class MyUser(AbstractBaseUser):
email = models.EmailField(
verbose_name='email address',
max_length=255,
unique=True,
)
date_of_birth = models.DateField()
...
One2One Relation with User model example:
from django.contrib.auth.models import User
class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
department = models.CharField(max_length=100)

Django accessing ManyToMany fields from post_save signal

I have a Django model and I want to modify the object permissions on or just after save. I have tried a few solutions and the post_save signal seemed the best candidate for what I want to do:
class Project(models.Model):
title = models.CharField(max_length=755, default='default')
assigned_to = models.ManyToManyField(
User, default=None, blank=True, null=True
)
created_by = models.ForeignKey(
User,
related_name="%(app_label)s_%(class)s_related"
)
#receiver(post_save, sender=Project)
def assign_project_perms(sender, instance, **kwargs):
print("instance title: "+str(instance.title))
print("instance assigned_to: "+str(instance.assigned_to.all()))
In this case, when a Project is created, the signal fires and I see the title, but an empty list for the assigned_to field.
How can I access the saved assigned_to data following save?
You're not going to. M2Ms are saved after instances are saved and thus there won't be any record at all of the m2m updates. Further issues (even if you solve that) are that you're still in a transaction and querying the DB won't get you m2m with proper states anyways.
The solution is to hook into the m2m_changed signal instead of post_save.
https://docs.djangoproject.com/en/dev/ref/signals/#m2m-changed
Your sender then would be Project.assigned_to.through
If your m2m can be empty (blank=True) you are in a little trouble with m2m_changed, because m2m_changed doesn't fire if m2m wasn't set. You can solve this issue by using post_save and m2m_changed at the same time. But there is one big disadvantage with this method - your code will be executed twice if m2m field isn't empty.
So, you can use transaction's on_commit (Django 1.9+)
Django provides the on_commit() function to register callback
functions that should be executed after a transaction is successfully
committed.
from django.db import transaction
def on_transaction_commit(func):
def inner(*args, **kwargs):
transaction.on_commit(lambda: func(*args, **kwargs))
return inner
#receiver(post_save, sender=SomeModel)
#on_transaction_commit
def my_ultimate_func(sender, **kwargs):
# Do things here
Important note: this approach works only if your code calls save().
post_save signal doesn't fire at all in cases when you call only instance.m2m.add() or instance.m2m.set().
Use transaction on commit!
from django.db import transaction
#receiver(post_save, sender=Project)
def assign_project_perms(sender, instance, **kwargs):
transaction.on_commit(lambda: print("instance assigned_to: "+str(instance.assigned_to.all())))
here is an example about how to use signal with many to many field (post like and post comments models),
and in my example i have :
like model (Intermediary table for User and Post tables) : the user can add 1 record only in Intermediary table for each post , which means (unique_together = ['user_like', 'post_like']) for this type of many to many relations you can use 'm2m_changed' signals ,
comment model (Intermediary table for User and Post tables): the user can add many records in Intermediary table for each post , (without unique_together ), for this i just use 'post_save, post_delete' signals , but you can use also 'pre_save, pre_delete' if you like ,
and here is both usage example :
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save, post_delete, m2m_changed
from django.dispatch import receiver
class Post(models.Model):
post_user = models.ForeignKey(User,related_name='post_user_related', on_delete=models.CASCADE)
post_title = models.CharField(max_length=100)
post_description = models.TextField()
post_image = models.ImageField(upload_to='post_dir', null=True, blank=True)
post_created_date = models.DateTimeField(auto_now_add=True)
post_updated_date = models.DateTimeField(auto_now=True)
post_comments = models.ManyToManyField(
User,
through="Comments",
related_name="post_comments"
)
p_like = models.ManyToManyField(
User, blank=True,
through="LikeIntermediary",
related_name="post_like_rel"
)
class LikeIntermediary(models.Model):
user_like = models.ForeignKey(User ,related_name="related_user_like", on_delete=models.CASCADE)
post_like = models.ForeignKey(Post ,related_name="related_post_like", on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.user_like} - {self.post_like} "
class Meta:
unique_together = ['user_like', 'post_like']
#receiver(m2m_changed, sender=LikeIntermediary)
def like_updated_channels(sender, instance, **kwargs):
print('this m2m_changed receiver is called, the instance is post id', instance.id)
class Comments(models.Model):
cmt_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="related_comments_user")
cmt_post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="related_comments_post")
cmt_created_date = models.DateTimeField(auto_now_add=True)
cmt_comment_body = models.TextField()
cmt_created = models.DateTimeField(auto_now_add=True)
cmt_updated = models.DateTimeField(auto_now=True)
#receiver(post_save, sender=Comments)
def comments_updated_channels(sender, instance, created, **kwargs):
print('this post_save receiver is called, the instance post id', instance.cmt_post.id)
#receiver(post_delete, sender=Comments)
def comments_deleted_channels(sender, instance, **kwargs):
print('this post_save receiver is called, the instance post id', instance.cmt_post.id)
notes :
the instance with 'm2m_changed' it is a post object .
the instance with 'post_save and post_delete' it is a comment object
this is just an example , and change it based on your case/requirements.
i hope this helpful