how to save a model object in another model save method - django

I have user model that has a one to one relation with two other models.
these are my models:
class User(AbstractUser):
id = models.AutoField(primary_key=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
isPreRegistered = models.BooleanField(default=False)
username = models.CharField(unique=True, max_length=13)
first_name = models.CharField(max_length=32, null=True, default=None)
last_name = models.CharField(max_length=64, null=True, default=None)
progress_level = models.CharField(max_length=25, null=True, choices=USER_PROGRESS_LEVELS)
class ScientificInfo(models.Model):
id = models.AutoField(primary_key=True)
user = models.OneToOneField(User, on_delete=models.CASCADE)
final_assessment = models.TextField(null=True, blank=True)
is_interviewed = models.BooleanField(default=False)
class PsychologicInfo(models.Model):
id = models.AutoField(primary_key=True)
user = models.OneToOneField(User, on_delete=models.CASCADE)
final_assessment = models.TextField(null=True, blank=True)
is_interviewed = models.BooleanField(default=False)
I want to update the user's progress_level if PsychologicInfo.is_interviewed and ScientificInfo.is_interviewed are both True. So I thought I should override the save method and added this to the user model:
def save(self, *args, **kwargs):
if self.scientificinfo.is_interviewed == True and self.psychologicinfo.is_interviewed == True:
self.progress_level = USER_PROGRESS_LEVELS[1][0]
return super(User, self).save(*args, **kwargs)
But I have to save the User object one more time to see some results. how can I update my progress level field when PsychologicInfo and ScientificInfo get saved?

I think you can use the Django signals a post_save can be what you need.
U can make a check if the instance PsychologicInfo or ScientificInfo
are updated or created then update the progress
voila an example of what you may want:
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import PsychologicInfo, ScientificInfo
# import your choices USER_PROGRESS_LEVELS
#receiver(post_save, sender=PsychologicInfo)
def pre_save_receiver(sender, instance, *args, **kwargs):
if instance:
if instance.psychologicinfo.is_interviewed:
instance.progress_level = USER_PROGRESS_LEVELS[1][0]
# Do other operation ...
You duplicate the this code by changing the sender and change the logique of your condition. it should work just fine

Related

AttributeError at /admin/accounts/user/1/change/ 'User' object has no attribute 'profile'

I am creating an API. When i create a new user, or edit an existing user and save, i get this error
"AttributeError at /admin/accounts/user/1/change/
'User' object has no attribute 'profile"
For clarity, I have a user model and a profile model lied o my User model with a onetoone relationship.
Here is my User model
artist_name = models.CharField(
max_length=255,
default='artist name',
unique=True
)
slug = models.SlugField(max_length=250, null=True, blank=True)
email = models.EmailField(max_length=300, unique=True)
active = models.BooleanField(default=True)
staff = models.BooleanField(default=False)
admin = models.BooleanField(default=False)
date_joined = models.DateTimeField(auto_now_add=True)
last_login = models.DateTimeField(auto_now_add=True)
My Profile model
class Profile(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE,
related_name="user",
default=1
)
first_name = models.CharField(max_length=255, blank=True, null=True)
last_name = models.CharField(max_length=255, blank=True, null=True)
country = models.CharField(max_length=500, blank=True, null=True)
gender = models.CharField(max_length = 20, blank=True, null=True)
record_label = models.CharField(max_length=255, blank=True, null=True)
image = models.FileField()
And my signals
from .models import User, Profile
from django.dispatch import receiver
#receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_profile(sender, instance, **kwargs):
instance.profile.save()
I should also mention that I don't have views for my user and profile yet. But i don't think that should affect the admin area.
So far i have searched online but nothing worked.
Thank you in anticipation.
i would advice you not to create a seperate user model, rather use the default User model. note that some of the features you need on your Profile model are actually present in the User model. in User model you have first_name, last_name,email,username, password1 and password2 by default but if you only what to use the User model you specified then follow the steps. so in your profile model the fields you should specify should be as follows:
from django.contrib.auth.models import User
from .models import User, Profile
from django.dispatch import receiver
from django.db.models.signals import post_save
class User(models.Model):
artist_name = models.CharField(max_length=255, default='artist name', unique=True)
slug = models.SlugField(max_length=250, null=True, blank=True)
email = models.EmailField(max_length=300, unique=True)
active = models.BooleanField(default=True)
staff = models.BooleanField(default=False)
admin = models.BooleanField(default=False)
date_joined = models.DateTimeField(auto_now_add=True)
last_login = models.DateTimeField(auto_now_add=True)
class Profile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
country = models.CharField(max_length=500, blank=True, null=True)
gender = models.CharField(max_length = 20, blank=True, null=True)
record_label = models.CharField(max_length=255, blank=True, null=True)
image = models.ImageField()
#receiver(post_save, sender=User)
def create_profile(sender, instance, created, *args, **kwargs):
if created:
Profile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_profile(sender, created, instance, **kwargs):
if not created:
instance.profile.save()
I hope this helps and if it does vote up this answer and give response.

Recording user activity in django?

I have a project in which some user can perform CRUD activities. I want to record who did what and when. Currently, I am thinking of making a model
class UserAction(models.Model):
user_id = models.CharField(max_length=100)
action_flag = models.CharField(max_length=100)
class_id = models.CharField(max_length=100)
action_taken_at = models.DateTimeField(default=datetime.now())
and making a function that fills my UserAction table. Is there any better way to do this?
app/models.py:
from django.db import models
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
class Action(models.Model):
sender = models.ForeignKey(User,related_name='user',on_delete=models.CASCADE)
verb = models.CharField(max_length=255)
target_ct = models.ForeignKey(ContentType, blank=True, null=True,
related_name='target_obj', on_delete=models.CASCADE)
target_id = models.PositiveIntegerField(null=True, blank=True)
target = GenericForeignKey('target_ct', 'target_id')
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('-created',)
def __str__(self):
return self.pk
app/admin.py
from .models import Action
admin.site.register(Action)
How you can use it ?
you can now import this models(Action) inside any of yours views.py.
Example if you have a post and a user likes it.you can just write
Action.objects.create(sender=request.user,verb="likes this post",target=post)
and now when you look at your admin you will see that tartget_id=post.pk
Here I assume that a user is authenticated and you can change it for your own.Happy coding!!!
You can do it by creating a model in
Models.py
class Auditable(models.Model):
ip = models.GenericIPAddressField(null=True)
user_agent = models.CharField(max_length=255, blank=True)
remote_host = models.CharField(max_length=255, blank=True)
created_at = models.DateTimeField(auto_now_add=True, blank=True, null=True)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING, related_name="%(app_label)s_%(class)s_created_by", null=True, blank=True) # this is for web user
modified_at = models.DateTimeField(auto_now=True, blank=True, null=True)
modified_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING, related_name="%(app_label)s_%(class)s_modified_by", null=True, blank=True) # this is for web user
class Meta:
abstract = True
def get_fields(self):
list_fields = ['ip', 'user_agent',
'remote_host', 'created_by', 'modified_by']
return [(field.verbose_name, field._get_val_from_obj(self)) for field in self.__class__._meta.fields if field.name not in list_fields and not
(field.get_internal_type() == "DateTimeField" and
(field.auto_now is True or field.auto_now_add is True)) and
field.concrete and (not field.is_relation or field.one_to_one or
(field.many_to_one and field.related_model))]
You can give any class name (i have given auditable). So all you have to do is pass this class (auditable) in your every model instead of models.Model
For Eg:
class Student(Auditable):
By doing this it will add all the auditable fields records in every table you have created.
Hope you may get your answer by doing this.

Django Signals: Can't Use For Loop?

When i remove the for loop in the signals then it works (creates an object properly) but when i use the for loop it should create an object for each post in the Collection object but this doesn't work. It doesn't even create an object of the Collection_List_Item model. Is there a reason why this for loop doesn't work? Is there a way to work around this?
models
class Collection(models.Model):
posts = models.ManyToManyField(Post, related_name='collection_posts', blank=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
collection_name = models.CharField(max_length=100)
collection_description = models.CharField(max_length=1000, blank=True)
collection_likes = models.ManyToManyField(User, related_name='liked_collections', blank=True)
collection_image = models.ImageField(upload_to="images/")
private = models.BooleanField(default=False)
follows = models.ManyToManyField(User, related_name='collection_follows', blank=True)
def __str__(self):
return self.collection_name
class Collection_List_Item(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, null=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
collection = models.ForeignKey(Collection, on_delete=models.CASCADE, null=True)
saved = models.BooleanField(default=False)
created_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now=True)
def __str__(self):
return self.collection.collection_name
signals:
#receiver(post_save, sender=Collection)
def create_collection_list_item(sender, instance, created, **kwargs):
if created:
for i in instance.posts.all():
collection_list_item = Collection_List_Item.objects.create(collection=instance, user=instance.author, post=i)
collection_list_item.save()
For ManyToManyField fields you have to use m2m_changed (Django Docs) signal.
Because ManyToManyField are saved after instance is saved and thus there won't be any record at all of the ManyToManyField updates.
from django.db.models.signals import m2m_changed
from django.db import IntegrityError
#receiver(m2m_changed, sender=Collection.posts.through)
def create_collection_list_item(sender, instance, action, *args, **kwargs):
if action == "post_add":
for i in instance.posts.all():
try:
collection_list_item = Collection_List_Item.objects.create(collection=instance, user=instance.author, post=i)
except IntegrityError:
pass

How to create model allowing infinite submodels in django?

Im creating a site where you can write down your goals, you should be able to split every goal into subgoals if chosen, and allow those subgoals to be split into subgoals infinitely.
This code below shows what i came up with first for the models, the first model is for creating a goal, the second model can either either be a subgoal of the goal or a subgoal of the subgoal.
But it seems like a really bad way to go around this problem.
Django semi-newbie BTW...
from django.db import models
from django.contrib.auth.models import User
class Goal(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, default=None)
title = models.CharField(max_length=70, null=True)
content = models.TextField(blank=True)
slug = models.SlugField(max_length=50, editable=False)
date_created = models.DateTimeField(auto_now_add=True, null=True)
class Meta:
unique_together = ['user', 'title']
def __str__(self):
return self.user.username + " - " + self.title
def save(self, *args, **kwargs):
self.slug = self.title.replace(' ', '-').lower()
super(Goal, self).save(*args, **kwargs)
class SubGoal(models.Model):
goal = models.ForeignKey(
Goal, on_delete=models.CASCADE, null=True, blank=True)
parent = models.ForeignKey(
"SubGoal", on_delete=models.CASCADE, null=True, blank=True)
title = models.CharField(max_length=70)
content = models.TextField(blank=True)
date_created = models.DateTimeField(auto_now_add=True, null=True)
def __str__(self):
try:
return self.goal.title + " - " + self.title
except:
return self.parent.title + " - " + self.title
You can make a ForeignKey to self. If the ForeignKey is NULL, then that goal has no parent, otherwise it refers to the parent:
from django.db import models
from django.contrib.auth.models import User
from django.conf import settings
django.utils.text import slugify
class Goal(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=70)
content = models.TextField(blank=True)
slug = models.SlugField(max_length=50, editable=False)
date_created = models.DateTimeField(auto_now_add=True)
parent = models.ForeignKey(
'self',
on_delete=models.SET_NULL,
null=True,
default=None,
related_name='subgoals'
)
class Meta:
constraints = [
models.UniqueConstraint(fields=['user', 'title'], name='user_title')
]
def __str__(self):
if self.parent_id is None:
return '{}-{}'.format(self.user.username, self.title)
else:
return '{}-{}'.format(str(self.parent), self.title)
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super(Goal, self).save(*args, **kwargs)
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.

Django: Is it Possible to create a model like Profile Model, to create an instance at the time of registration automatically

I have a custom user model and has created a profile model from it as well. so when user sign up a profile instance is created in the profile model as well. Now I have another similar model which is the address model. I tried configuring it in the same way but the address instance isn't getting created. Is it possible to do that? This is just for an understanding, whether similar model like profile can be created.
this is my model.
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
summary = models.TextField(max_length=250, blank=True, null=True)
birth_date = models.DateField(null=True,blank=True, auto_now_add=False, auto_now=False)
country = CountryField(blank_label='(select country)')
profile_pic = models.ImageField(upload_to='pimage/', default='pimage/default.png')
def __str__(self):
return f'{self.user.username} Profile'
class Address(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE, blank=True, null=True)
address = models.CharField(max_length=200, null=True)
city = models.CharField(max_length=200, null=True)
state = models.CharField(max_length=200, null=True)
zipcode = models.CharField(max_length=200, null=True)
mobile = models.CharField(max_length=12, null=True)
def __str__(self):
return f'{self.user.username} Address'
views
#login_required
def address_add(request):
if request.POST:
a_form = AddressForm(request.POST, instance=request.user.address)
if a_form.is_valid():
a_form.save()
messages.success(request, f'Address Saved')
return redirect('user_address')
else:
a_form = AddressForm(instance=request.user.address)
context = {'a_form':a_form,}
return render(request, 'áccounts/user_address.html', context)
You can override save() method of User model to create an Address instance automatically:
from django.db import models
class User(models.Model):
# Your user model fields
def save(self, *args, **kwargs):
if self._state.adding:
# Create your Address instance here
pass
super(User, self).save(*args, **kwargs)
If you want to know how you can extend the Django user model here is a link that can help you.
Even though you can define some signal to create your Address instance after an User instance has been created.
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
class Address(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(max_length=500, blank=True)
location = models.CharField(max_length=30, blank=True)
birth_date = models.DateField(null=True, blank=True)
#receiver(post_save, sender=User)
def create_user_address(sender, instance, created, **kwargs):
if created:
Address.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_address(sender, instance, **kwargs):
instance.address.save()