Signals in Django - django

I have 2 models.py files in different app directories: users.models.py and friends.models.py.
There is one problem: if some user deleted from UserProfile model, all his friendship network must be deleted too with him. It is very natural.
But when I import Frienship to users.model.py I've got an error: Cannot import name Friendship
I understand an error arose because of mutual importing in these 2 files, and I know I can easely solve this problem with the help of signals, but I do not know how to do it in proper way.
Could anybody help in this particular case?
In users.models.py:
from friends.models import Friendship
class UserProfile(models.Model):
username = models.Charfield(max_length=50)
...
def delete(self, *args, **kwargs):
Friendship.objects.remove_all(self)
self.delete(*args, **kwargs)
In friends.models.py:
from users.models import UserProfile
class FriendshipManager(models.Manager):
def remove_all(self, user):
usr = Friendship.objects.get(user=user).friends
frs = [i.user for i in usr.all()]
for fr in frs:
usr.remove(fr)
class Friendship(models.Model):
user = models.Foreignkey(UserProfile)
friends = models.ManyToManyField('self')
objects = FriendshipManager()
Thanks in advance!!!

No need for signals, just use the magic *_set property. It will be defined at runtime so you don't have to worry about circular imports.
friends/models.py
from users.models import UserProfile
class Friendship(models.Model):
user = models.Foreignkey(UserProfile)
friends = models.ManyToManyField('self')
users/models.py
class UserProfile(models.Model):
username = models.Charfield(max_length=50)
...
def delete(self, *args, **kwargs):
for f in self.friendship_set.all():
f.delete()
super(self.__class__, self).delete(*args, **kwargs)

This is what I usually do, you could easily refactor this to implement with your model
from django.db import models
from django.db.models.signals import post_save, post_delete
from django.contrib.auth.models import User
# App specific
class UserProfile(models.Model):
"""
Adds a basic UserProfile for each User on the fly.
"""
user = models.OneToOneField(User)
avatar = models.ImageField(upload_to='example/somewhere', blank=True)
def __str__(self):
return "%s's profile" % self.user
def get_avatar(self):
return '/'+self.avatar
def clean_avatar(self):
# TODO: imagekit
pass
class Meta:
app_label = 'users'
def save(self, *args, **kwargs):
if self.id:
this = UserProfile.objects.get(id=self.id)
if this.avatar:
if this.avatar != self.avatar:
this.avatar.delete(save=False)
super(UserProfile, self).save()
def create_user_profile(sender, instance, created, **kwargs):
if created:
profile, created = UserProfile.objects.get_or_create(user=instance)
post_save.connect(create_user_profile, sender=User)
def UserProfileDelete(instance, **kwargs):
"""
Documentatie
"""
instance.avatar.delete(save=False)
post_delete.connect(UserProfileDelete, sender=UserProfile)

Related

Django model save related model

I have three models, Person, Role and Student. Person is related to role with ManyToManyField and Student inherits from Person. The code:
class Person(models.Model):
#fields
class Role(models.Model):
ROLE_CHOICES = (
('AL', 'ALUMNO'),
#... more choices
)
person = models.ManyToManyField(Person)
role = models.CharField(
'Rol',
max_length = 2,
choices = ROLE_CHOICES
)
class Student(Person):
# fields
def save(self, *args, **kwargs):
super(Student, self).save(*args, **kwargs)
# what code should I put here to save role?
I want to, on saving a Student, automatically save a role for him/her ('AL'). Also it has to execute on create and not in update.
I've seen other posts addresing this, but it remains unclear to me how
to implement this.
As I understand, I can override the save method, but I'm not sure how exactly do this. I'm aware that post_save signal can also accomplish this, but I'm not sure how either.
Thanks.
You can't do this before student get it's pk by save method,because m2m relations is establish by your instance pk and pk is generate after your instance save to db.
two way to archieve:
First one:
new a signals.py file in your app:
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import *
#receiver(post_save, sender=Student)
def create_student(sender, instance=None, created=False, **kwargs):
if created:
role, is_created = Role.objects.get_or_create(name='AL')
role.person.add(instance)
and in your apps.py
class UserConfig(AppConfig):
name = 'user'
def ready(self):
import user.signals
replace User with your own app_label.
Second one:
after django 1.9,django has transaction tool allow you performing actions after commit.doc is here:
from django.db import transaction
class Student(Person):
.
.
.
def save(self, *args, **kwargs):
instance = super(Student, self).save(*args, **kwargs)
if not self.pk:
# do when create
transaction.on_commit(self.update_role)
return instance
def update_role(self):
# this will be call after instance save to db
role, is_created = Role.objects.get_or_create(name='AL')
role.person.add(self)
all code is untested.

Updating the a user profile upon user save

I'm following the 'User profile' approach to extend my User model, like so:
# models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE, primary_key=True)
my_field = models.CharField(max_length=100)
# signals.py
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
With this approach, I have to explicitly call user.profile.save(), which to me feels clunky, as I want the profile to give the illusion it is part of the User object:
# views.py
def some_func(request):
user = User.objects.create_user('dummy', 'dummy#dummy.com', '12345678')
user.profile.my_field = 'hello'
user.save() # This does not persist the profile object...
user.profile.save() # ...this does
To remedy this, I've changed create_user_profile() to the following, which works:
# signals.py
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
profile = UserProfile.objects.get_or_create(user=instance)
profile.save()
Numerous examples I've encountered do not use this approach. Are there any caveats to using this approach?
The better way is to specify a custom user model.
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
custom_field = models.ForeignKey(
'contracts.Contract'
)
...
class Meta(AbstractUser.Meta):
swappable = 'AUTH_USER_MODEL'
You have to update the settings.py defining the AUTH_USER_MODEL property:
AUTH_USER_MODEL = 'app_name.User'
You can use a custom User model like this :
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
def __str__(self):
return f'{self.user.username} Profile'
and then the signals.py file :
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.dispatch import receiver
from .models import Profile
#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()
what the signals.py file does here is to inform you when a new Profile object is created of the User type which you can further use to create forms that update/create the user's profile.
And to make all this work, you need to import the signals.py file in the apps.py file of your app. For example, this is what your apps.py file would look like :
from django.apps import AppConfig
class UsersConfig(AppConfig):
name = 'users'
def ready(self):
import users.signals
Yes, there are a few. In the following situations the post_save signal would not be fired.
1 If the save method does not successfully save the object (such as when an IntegrityError occurs)
2 When you call MyModel.objects.update()
3 When you override the save method and forget to call the superclass method.
4 When your signal receiver hasn't been successfully registered.
In these situations your profile wouldn't be saved.

Using a SELECT field for a OneToMany field

let's say I've the following very simple models:
class Customer(models.Model):
name = models.CharField(max_length=50)
class Probe(models.Model):
OwnerInfo = models.CharField(max_length=50)
comments = models.CharField(max_length=50)
customer = models.ForeignKey(Customer, null=True, blank=True)
I've been able to add an InLine to the Admin gui, but I'd like to use a SELECT component, so I can just select several Probes and assign them to the Customer. From this question:
one-to-many inline select with django admin
I know thanks to Luke's answer (last one) that I should create a custom Form and assign it to my ModelAdmin.form but I can not wonder how to tie it all together to make it work.
May anyone help?
Thanks a lot in advance.
OK, I came a step further, and now I've the field added to the Form, like this:
from django.contrib import admin
from django import forms
from web_gui.models import Probe, Customer, Firmware
class CustomerForm(forms.ModelForm):
probes = forms.ModelMultipleChoiceField(queryset=Probe.objects.all())
def __init__(self, *args, **kwargs):
super(CustomerForm, self).__init__(*args, **kwargs)
self.fields['probes'].initial = [p.pk for p in Probe.objects.filter(customer_id=self.instance.pk)]
class Meta:
model = Customer
class CustomerAdmin(admin.ModelAdmin):
form = CustomerForm
admin.site.register(Probe)
admin.site.register(Customer, CustomerAdmin)
admin.site.register(Firmware)
but the initial values specified through "initial" are not being selected. What's wrong now? I assume that next will be to override the save() method to set the Probes on the Customer, am I right?
This is the best solution I've came up with. Let me know if there is any other better way of achieving this:
from django.contrib import admin
from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
from web_gui.models import Probe, Customer, Firmware
class CustomerForm(forms.ModelForm):
probes = forms.ModelMultipleChoiceField(queryset = Probe.objects.all(), required=False)
probes.widget = FilteredSelectMultiple("Probes",False,attrs={'rows':'10'})
def __init__(self, *args, **kwargs):
super(CustomerForm, self).__init__(*args, **kwargs)
self.fields['probes'].initial = [p.pk for p in Probe.objects.filter(customer_id=self.instance.pk)]
def save(self, force_insert=False, force_update=False, commit=True):
c = super(CustomerForm, self).save(commit=False)
c.probe_set = self.cleaned_data['probes']
c.save()
return c
class Meta:
model = Customer
class CustomerAdmin(admin.ModelAdmin):
form = CustomerForm
admin.site.register(Probe)
admin.site.register(Customer, CustomerAdmin)
admin.site.register(Firmware)

Overriding save() method in Model. How to do this correctly?

I try overriding save() method in Model. How to do this correctly?
import Image
from tesseract import image_to_string
class FileModel(models.Model):
user = models.ForeignKey(User)
base_file = models.FileField(upload_to="files")
after_file = models.FileField(upload_to="file_ocr", blank=True, null=True)
def save(self):
after_file = image_to_string(Image.open('base_file'), lang='en')
after_file.save()
super(FileModel, self).save()
If base_file is created I need use image_to_string() method and save result like after_file.
def save(self, *args, **kwargs):
creating = self.pk is None
super(FileModel, self).save(*args, **kwargs)
if creating:
self.after_file = image_to_string(Image.open('base_file'), lang='en')
self.after_file.save()
or with signals:
from django.db.models import signals
from django.dispatch import receiver
#receiver(signals.post_save, sender=FileModel)
def create_file(sender, instance, created, *args, **kwargs):
if created:
instance.after_file = image_to_string(Image.open('base_file'), lang='en')
instance.after_file.save()

Django: How can I use get_model in my use case to correctly import a class and avoid conflicts

I have a model with a get_form method.
# models.py
from model_utils.managers import InheritanceManager
from breathingactivities.forms import ParentRecordForm, ChildRecordForm
class ParentRecord(models.Model):
....
objects = InheritanceManager()
def get_fields(self, exclude=('user')):
fields = self._meta.fields
return [(f.verbose_name, f.value_to_string(self)) for f in fields if not exclude.__contains__(f.name)]
#classmethod
def get_form(self, *args, **kwargs):
return ParentRecordForm
class ChildRecord(ParentRecord):
....
duration = DurationField(
_('Duration'),
help_text=_('placeholder'))
#classmethod
def get_form(self, *args, **kwargs):
return ChildRecordForm
I have a view that uses this get_form method to determine to correct form for a given object.
# views.py
class ParentRecordUpdateView(UpdateView):
model = ParentRecord
form_class = ParentRecordForm
template_name = 'parentrecords/create.html'
def get_object(self, **kwargs):
return ParentRecord.objects.get_subclass(slug=self.kwargs['slug'])
def get_form_class(self, *args, **kwargs):
form_class = self.model.objects.get_subclass(slug=self.kwargs['slug']).get_form()
return form_class
def get_form_kwargs(self):
kwargs = super(ParentRecordUpdateView, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs
I am using InheritanceManager from django-model-utils so I get a clean API to subclasses when I query a parent class - that is the get_subclass() stuff, and this is why my view works with ParentRecord.
All this works good. I see via console that indeed form_class is the form class I'd expect, for example, ChildRecordForm when the instance is of ChildRecord.
In my forms.py, I can't just import models.ParentRecord and models.ChildRecord, as I import those forms in my models.py, and thus an error is raised that Django can't import these models. I presume because of circular imports.
So, I try this instead:
# forms.py
from django.db.models import get_model
class ParentRecordForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
super (ParentRecordForm, self).__init__(*args, **kwargs)
class Meta:
model = get_model('model', 'ParentRecord')
exclude = ('user')
class ChildRecordForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
super (ChildRecordForm, self).__init__(*args, **kwargs)
class Meta:
model = get_model('model', 'ParentRecord')
exclude = ('user')
However, model for these forms always returns None.
I I go and pass ChildRecordForm some totally unrelated model that I can import from a different app in my project, for example:
# forms.py
from another_app import AnotherModel
class ChildRecordForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
super (ChildRecordForm, self).__init__(*args, **kwargs)
class Meta:
model = AnotherModel
exclude = ('user')
Then it works, meaning, The form returns the fields of AnotherModel.
So, I can't work out why get_model() works for me in shell, but not in a form class when I use it to declare a value for a model.
My guess on get_model() is that running it at the class-definition level can give incorrect results, since it will get evaulated as Django is populating all the models in it's AppCache. My quick reading of the django.db.models.loading module doesn't seem to show that issue, but one thing to try is to run get_model() inside a view and print out the results to see if it is what you think it should be, since by that time the AppCache should be fully loaded.
But - as a workaround to get around the original circular import (so you don't have to use get_model anyway) is to not do the form imports at the module level - you can stick them in the classmethod instead:
class ParentRecord(models.Model):
#classmethod
def get_form(self, *args, **kwargs):
from yourapp.forms import BreathingActivityRecordForm
return BreathingActivityRecordForm
This way, the import will only be evaulated when you actually call .get_form(), and there shouldn't be any circular dependencies at module loading tme.