Using signals for two models both inheriting from User in Django - django

Suppose we have two models that have signal to the User model:
from django.db import models
from django.contrib.auth.models import User
from django.db.models import signals
class Company(User):
name = models.CharField(null=True, blank=True, max_length=30)
if created:
Company.objects.create(
user_ptr_id=instance.id,
username=instance.username,
password=instance.password,
email=instance.email,
first_name=instance.first_name,
last_name=instance.last_name,
is_active=instance.is_active,
is_superuser=instance.is_superuser,
is_staff=instance.is_staff,
date_joined=instance.date_joined,
)
signals.post_save.connect(
create_company, sender=User, weak=False, dispatch_uid="create_companies"
)
class Individual(User):
name = models.CharField(null=True, blank=True, max_length=30)
def create_job_seeker(sender, instance, created, **kwargs):
"""
:param instance: Current context User instance
:param created: Boolean value for User creation
:param kwargs: Any
:return: New Seeker instance
"""
if created:
'''
we should add a condition on whether the Company uses the same username
if true, then, we must not create a JobSeeker and we would disable the account using
Firebase Admin
'''
JobSeeker.objects.create(
user_ptr_id=instance.id,
username=instance.username,
password=instance.password,
email=instance.email,
first_name=instance.first_name,
last_name=instance.last_name,
is_active=instance.is_active,
is_superuser=instance.is_superuser,
is_staff=instance.is_staff,
date_joined=instance.date_joined,
)
signals.post_save.connect(
create_job_seeker, sender=User, weak=False, dispatch_uid="create_job_seekers"
)
Now, each time a User is created we should be allowed to extend it through both Individual and Company models. But, I want to prohibit the usage of both objects. User can either have a Company or an Individual object to be edited not both. Should I override the save method such as this:
def save(self, *args, **kwargs):
if not Company.objects.filter(username=self.username).exists():
super(Model, self).save(*args, **kwargs)
else:
raise 'Some error'
Or should I add a condition on the created method such as this:
...
if created and Company.objects.filter(username, self.username).exists() == False:
Company.objects.create(
...
Which approach is better? And is there another approach that you might suggest?

Signals, for most cases, I believe are the best way to handle sharing data between models assuming the CRUD for each related model isn't done together. So post_save, pre_save, post_delete, pre_delete and so are typically the best way to go about handling data that any given model instance relies on. This can be true about manipulating model data after a save. Signals were designed specifically for this reason. The other great thing about signals is you can connect them throughout your project and not necessarily just where the Model is defined. Just import the model and the signal you want to connect to it and bam!
How to use signals? follow django's documentation here. it's very simple
https://docs.djangoproject.com/en/4.0/topics/signals/

Related

Django why model foreign key cascade will not trigger delete?

there two basic ways to do something when an instance gets deleted:
Overwrite Model.delete
Signal
I used to reckon both of them serve the same purpose, just provides different ways of writing, but works exactly.
However, in this occasion, I realise I was wrong:
class Human(models.Model):
name = models.CharField(max_length=20)
class Pet(models.Model):
name = models.CharField(max_length=20)
owner = models.ForeignKey(Human, related_name="pet", on_delete=models.CASCADE)
def delete(self, *args, **kwargs):
print('------- Pet.delete is called')
return super().delete(*args, **kwargs)
h = Human(name='jason')
h.save()
p = Pet(name="dog", owner=h)
p.save()
h.delete()
# nothing is shown
Why Pet.delete Is not firing at Human.delete By the foreign cascade? Does I have to apply a signal on this? If so, would it cost more performance?
I am building something very heavy, comment system, filter decent records and delete when the commented target get deleted, the comment model has many null-able foreign key fields, with models.CASCADE Set, only one of them is assigned with value. But in product delete view, I call product.delete Then triggers cascade, but comment.delete Is not firing.
Currently, the project has delete Defined on many models, with assumption that it is always triggered when the instance get removed from database, and it is tremendous work to rewrite it in signal. Is there a way to call delete When at cascading? (I know it is likely impossible since it is a database field specification)
I implement a mix-in for Commendable models with extra methods defined, therefore, I decided to modify delete method to signal to something like this:
from django.db import models
from django.dispatch import receiver
from django.db.models.signals import pre_delete
# Create your models here.
class Base:
def __init_subclass__(cls):
#receiver(pre_delete, sender=cls)
def pet_pre_delete1(sender, instance, **kwargs):
print('pet pre delete 1 is called')
#receiver(pre_delete, sender=cls)
def pet_pre_delete2(sender, instance, **kwargs):
print('pet pre delete 2 is called')
class Human(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return f'<human>{self.name}'
class Pet(Base, models.Model):
name = models.CharField(max_length=20)
owner = models.ForeignKey(Human, related_name="pet", on_delete=models.CASCADE)
def __str__(self):
return f'<pet>{self.name}'
# ------- Pet.delete is called
# pet pre delete 1 is called
# pet pre delete 2 is called
it works fine in testing, I wonder if there is any risk using this, would it be garbage collected?

Django create linked models using receiver or overwriting save?

Currently on my first advanced Django project and have what seems to be more of an architectural question. I have two models that represent an approval process and are related as follows:
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
class Transaction(models.Model):
uid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
requester = models.ForeignKey(User, on_delete=models.PROTECT)
type = models.ForeignKey(TransactionType, on_delete=models.PROTECT)
class TransactionApproval(models.Model):
ACTIONS = (
(0, 'Declined'),
(1, 'Approved'),
)
transaction = models.ForeignKey(Transaction, on_delete=models.PROTECT)
user = models.ForeignKey(User, on_delete=models.PROTECT, related_name='approval_user')
action = models.PositiveSmallIntegerField(choices=ACTIONS, null=True, blank=True)
#receiver(post_save, sender=Transaction)
def create_transaction_approval(sender, instance, created, **kwargs):
""" Automatically adds approvers to the transaction when it's created. """
if created:
approvers = instance.requester.profile.get_manager()
logger.info('[TRANSACTION] Adding managers {0} to approval list (UUID {1})'.format(str(approvers), instance.uid))
for approver in approvers:
TransactionApproval.objects.create(transaction=instance, user=approver)
Now I've been creating the TransactionApprovals automatically when a certain Transaction is created using #receiver as outlined above as approvals are a crucial and required part of Transactions. However I'm not sure if it actually doesn't make more sense move these actions to the already overwritten def save() method of Transactions, to avoid scattering logic and linking back and forth to models. Which of the two is architecturally cleverer and more standard-compliant?
""" Alternative def save(): method of Transaction """
def save(self, *args, **kwargs):
logger.info('[TRANSACTION] {0} created transaction (UUID {1})'.format(self.requester, self.uid))
if not self.pk:
super.save()
approvers = self.requester.profile.get_manager()
logger.info('[TRANSACTION] Adding managers {0} to approval list (UUID {1})'.format(str(approvers), instance.uid))
for approver in approvers:
TransactionApproval.objects.create(transaction=self, user=approver)
else:
super.save()
# Other logic dealing with whether or not a transaction is fully approved
PS: Any other architectural leads are also appreciated.
I would avoid dealing with signals whenever I can. As cited on the documentation signals tend to duplicate themselves.
So, between the two I would choose overriding save method.
But as a better practice and to be more sure;
Consider a case where you have already deployed the project, and on the production a problem occured, so you have to create a transaction_approval without automatically adding approvers.
Rather than automatically adding the approvers on model or db signal level, you should handle the issue on view level.
Hope this helps!

Django model validate a ManyToMany field before adding

I have a model that looks somewhat like this:
class Passenger(models.Model):
name = models.CharField(max_length=50)
surname = models.CharField(max_length=50)
class Flight(models.Model):
capacity = models.IntegerField()
passengers = models.ManyToManyField(Passenger)
Before adding a new passenger to the flight I would like to validate whether the number of passengers is not going to exceed the capacity. I was wondering what would be the best way to go about this.
Obviously I could manually check the number of passengers before adding a new one, but maybe there is some support in django? I tried writing a validator, but wasn't sure how to do it.
According to Django docs you can listen to the m2m_changed signal, which will trigger pre_add and post_add actions.
Using add() with a many-to-many relationship, however, will not call
any save() methods (the bulk argument doesn’t exist), but rather
create the relationships using QuerySet.bulk_create(). If you need to
execute some custom logic when a relationship is created, listen to
the m2m_changed signal, which will trigger pre_add and post_add
actions.
According to #M.Void answer – Code Example:
from django.db import models
from django.db.models.signals import m2m_changed
from django.core.exceptions import ValidationError
class MyModel(models.Model):
m2mField = models.ManyToManyField('self')
m2mFieldLimit = 2
def m2mField_changed(sender,**kwargs):
instance = kwargs['instance']
if len(instance.m2mField.all()) >= instance.m2mFieldLimit :
raise ValidationError(f'Max number of records is {instance.m2mFieldLimi}')
m2m_changed.connect(commonobjects_changed,sender=MyModel.m2mField.through)
Override the clean method on the model to do the check you want:
class Passenger(models.Model):
name = models.CharField(max_length=50)
surname = models.CharField(max_length=50)
def clean(self, *args, **kwargs):
# clean gets called automatically by other things, so we can't always
# expect flight_id to be provided
if 'flight_id' in kwargs:
flight = Flight.objects.get(pk=kwargs['flight_id'])
if flight.passengers.all().count() >= flight.capacity:
# flight is full!
raise ValidationError
super(Passenger, self).clean()
class Flight(models.Model):
capacity = models.IntegerField()
passengers = models.ManyToManyField(Passenger)
Note that to do this, you will have to pass in the flight ID when validating the passenger:
f = Flight.objects.get(...)
p = Passenger(name='First', surname='Last')
try:
p.clean(flight_id=f.id) # full_clean calls clean, among other validations
p.save()
except ValidationError as e:
# do something to handle the error
Note that it is possible in multi-threaded applications for something to get validated successfully, but still fail to save in a race condition. You would need to add additional code to handle that.
See here for details on model validation.

Issues with admin part

I am developing an app which extends the auth_user table, whenever any user register to the app, its one instance is created in the extended table,It works very well from simple url(manually), but when I login from admin side and add user it will also create the instance in extended table but does not show or take the user name, it just shows the blank row.
model.py code is as:
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
class Drinker(models.Model):
user =models.OneToOneField(User)
birthday =models.DateField()
name =models.CharField(max_length=100)
def __unicode__(self):
return self.name
#create our user object to attach to our drinker object
def create_drinker_user_callback(sender, instance, **kwargs):
drinker, new=Drinker.objects.get_or_create(user=instance)
post_save.connect(create_drinker_user_callback, User)
the last 3 lines three create the instance in extended table drinker but does not shows any value just take the user_id from auth_user table. it does not take name and birthday
You're not populating the Drinker.name field.
The row is blank because the unicode() method is returning nothing.
One way to solve this would be to set it explicitly in the signal handler:
def create_drinker_user_callback(sender, instance, **kwargs):
drinker, new=Drinker.objects.get_or_create(user=instance, name=instance.get_full_name())
post_save.connect(create_drinker_user_callback, User)
or ditch the (possibly redundant) name field altogether and normalise it a bit:
class Drinker(models.Model):
user = models.OneToOneField(User)
birthday = models.DateField()
def __unicode__(self):
return self.user.get_full_name()

Define an attribute in a model, like an object of the other model in Django

Is posible to define an attribute in a data model like an object of other data model in Django?
This is the scenary:
models.py
class Inmueble(models.Model):
calle = models.CharField(max_length=20, verbose_name="Calle")
numero = models.CharField(max_length=6, verbose_name="Numero")
piso = models.IntegerField(verbose_name="Piso", blank=True, null=True)
galeria_id = models.OneToOneField(Galeria, verbose_name="Galería del Inmueble")
class Galeria(Gallery):
nombre = models.CharField(max_length=30, verbose_name="Nombre")
The point is: I need to create a new Galeria object automatically every time an Inmueble object is created. Thanks in advance!
Analía.
There are two ways to handle this:
Override the save() method for the Inmueble model.
Create a signal handler on Galeria that receives signals emitted by Inmueble
Both methods would work and are acceptable, however I recommend using a signal for a couple reasons:
It's a bit more de-coupled. If later you change or remove Galeria, your code doesn't break
The signal handler for postsave includes a boolean value to indicate whether the model is being created or not. You could technically implement the same functionality in model save() by checking if the model has a .id set or not, but IMO the signal is a cleaner solution.
Here's an idea of the code for both of these...
Using a Signal (recommended)
from django.db.models.signals import post_save
from wherever.models import Inmueble
class Galeria(Gallery):
# ...
def inmueble_postsave(sender, instance, created, **kwargs):
if created:
instance.galeria_id = Galeria.objects.create()
instance.save()
post_save.connect(inmueble_postsave, sender=Inmueble, dispatch_uid='galeria.inmueble_postsave')
Overriding Model save() Method
from wherever.models import Galeria
class Inmueble(models.Model):
# ...
def save(self, force_insert=False, force_update=False):
# No Id = newly created model
if not self.id:
self.galeria_id = Galeria.objects.create()
super(Inmueble, self).save()
Maybe
AutoOneToOneField is the answer.
Finally, I did:
from django.db.models.signals import post_save
class Galeria(Gallery):
inmueble_id = models.ForeignKey(Inmueble)
def inmueble_postsave(sender, instance, created, **kwargs):
if created:
instance = Galeria.objects.create(inmueble_id=instance, title=instance.calle+' '+instance.numero, title_slug=instance.calle+' '+instance.numero)
instance.save()
post_save.connect(inmueble_postsave, sender=Inmueble, dispatch_uid='galeria.inmueble_postsave')