Trigger a function in a Django model every time its variables change - python

I'm working on a Django model and I want to trigger a function every time its variables change. Here's my model:
class Cv(models.Model):
name = models.CharField(max_length=100)
position = models.ForeignKey(
OpenPosition,
on_delete=models.CASCADE,
null=True,
)
team = models.ForeignKey(
Team,
on_delete=models.CASCADE,
null=True
)
leader = models.ForeignKey(Leader, on_delete=models.CASCADE)
email = models.EmailField(
max_length=100,
null=False,
validators=[EmailValidator()],
blank=True
)
email_sent_status = models.BooleanField(default=False)
phone = models.CharField(
validators=[
RegexValidator(
regex=r'^(0\d{9,11})$',
message='''
Phone number must be started with 0 and has 10-12 digits
'''
)],
blank=True,
null=True,
max_length=20,
)
link = models.CharField(
max_length=150,
validators=[URLValidator()],
)
source = models.ForeignKey(CvSource, on_delete=models.CASCADE)
cv_status = models.CharField(
max_length=10,
choices=STATUS,
default='u',
)
schedule = models.DateTimeField(blank=True, null=True)
schedule_confirm = models.BooleanField(default=False)
interview_status = models.CharField(
max_length=10,
choices=FULL_STATUS,
default='u',
)
test_status = models.CharField(
max_length=10,
choices=STATUS,
default='u',
)
test_comment = models.TextField(max_length=200, blank=True)
decision_status = models.CharField(
max_length=10,
choices=FULL_STATUS,
default='u',
)
offer_status = models.CharField(
max_length=10,
choices=OFFER_STATUS,
default='u,'
)
internal_comment = models.TextField(max_length=200, blank=True)
created_at = models.DateTimeField(
editable=False,
null=True,
)
updated_by = models.CharField(max_length=30, editable=False, null=True)
Currently I'm using an approach that is mentioned in Django: When saving, how can you check if a field has changed? but with many variables this process become repetitive:
def __init__(self, *args, **kwargs):
super(Cv, self).__init__(*args, **kwargs)
#override original fields' values with current values
self.__original_schedule = self.schedule
self.__original_schedule_confirm = self.schedule_confirm
self.__original_name = self.name
self.__original_position = self.position
self.__original_team = self.team
self.__original_leader = self.leader
self.__original_email = self.email
self.__original_email_sent_status = self.email_sent_status
self.__original_phone = self.phone
#etc
def save(self, *args, **kwargs):
if self.__original_schedule != self.schedule:
#do something etc
I want to find a way to optimize this process. I have read somewhere that you can use #property to track variables' changes and I want to know if I can use this approach in my model. If not, is there any other way i can improve my method.

Why not django signals? see docs here
post_save signal is fired every time you save a model object.
Working example :
In your models.py add..
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=Cv)
def hear_signal(sender , instance , **kwargs):
if kwargs.get('created'):
return # if a new model object is created then return. You need to distinguish between a new object created and the one that just got updated.
#Do whatever you want.
#Your trigger function content.
#Parameter "instance" will have access to all the attributes of the model being saved. To quote from docs : It's "The actual instance being saved."
return
Thanks. Hope this helps.

Related

automatically add a new models to admin page

i have a calculator app, inside it i have a Transaction , Family_group , Family_member models see pic below.
i want everytime i try to make a new Transaction there will be a default 1 Family_group and Family_member added automatically, without me starting one each time. is there any way to do it ?
models.py
class Transaction(models.Model):
income_period_choices = (('Weekly', 'Weekly'), ('Fortnightly',
'Fortnightly'))
chp_reference = models.CharField(max_length=50, unique=True)
rent_effective_date = models.DateField(null=True, blank=True)
income_period = models.CharField(max_length=11,
choices=income_period_choices,
null=True,
blank=True, default='Weekly')
property_market_rent = models.DecimalField(help_text='Weekly',
max_digits=7,
decimal_places=2,
null=True,
blank=True)
class FamilyGroup(models.Model):
name_choices = (('FG_1', 'FG_1'), ('FG_2',
'FG_2'), ('FG_3', 'FG_3'), ('FG_4',
'FG_4'), ('FG_5', 'FG_5'))
name = models.CharField(max_length=10, choices=name_choices)
transaction = models.ForeignKey(Transaction, on_delete=models.CASCADE)
family_type = models.ForeignKey(FamilySituation,
on_delete=models.PROTECT,
null=True,
blank=True)
class FamilyMember(models.Model):
transaction = models.ForeignKey(Transaction, on_delete=models.CASCADE)
family_group = models.ForeignKey(FamilyGroup,
on_delete=models.CASCADE,
null=True,
blank=True)
name = models.CharField(max_length=100, null=True, blank=True)
date_of_birth = models.DateField(null=True, blank=True)
relationship = models.ForeignKey(Relationship, on_delete=models.PROTECT)
Without seeing your code, it's hard to help, but this might get you started:
You can override the save method on the Transaction model like so:
class Transaction(models.Model):
...
def save(self, *args, **kwargs):
# add / create family group if it is missing:
if not self.family_group:
self.family_group = <the Family_group instance>
# add / create family member if it is missing:
if not self.family_group.family_member_set.all()
self.family_group.add([<the Family_member instance>])
self.family_group.save()
return super(Transaction, self).save(*args, **kwargs)

How can i get in choices=max_guest by room_id when I create django.models?

I have model like this:
class KeyTransfer(Model):
key_out_data = DateTimeField(null=True, blank=True)
key_in_data = DateTimeField(null=True, blank=True)
room_id = ForeignKey(Room, blank=True, null=True, on_delete=CASCADE)
guests = IntegerField(choices=[(x, str(x)) for x in range(Room.objects.get(number=room_id).max_guests)], blank=True, null=True)
notes = CharField(max_length=256, blank=True)
person_id = ForeignKey(Person, null=True, blank=True, on_delete=SET_NULL)
and i understand that i can`t save some value in column "guests" with argument "choices=" as you can see above.
In a consequence I have gotten error:
...django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet. ...
and i see what this error means.
The question is: can I realise some similar condition for "choices" in "guests" do not using django.forms?
It seems that you want to set upper limit for guests field of KeyTransfer model based on its related room model's max_guests field. You can do that by overriding save method and check there if assigned guests is higher than max_guests. See below implementation of this approach:
from django.db import models
from django.core.exceptions import ValidationError
class Person(models.Model):
name = models.CharField(max_length=32)
def __str__(self):
return "<Person {}>".format(self.name)
class Room(models.Model):
max_guests = models.IntegerField()
class KeyTransfer(models.Model):
key_out_data = models.DateTimeField(null=True, blank=True)
key_in_data = models.DateTimeField(null=True, blank=True)
room_id = models.ForeignKey('Room', blank=True, null=True, on_delete=models.CASCADE)
guests = models.IntegerField(blank=True, null=True)
notes = models.CharField(max_length=256, blank=True)
person_id = models.ForeignKey('Person', null=True, blank=True, on_delete=models.SET_NULL)
def save(self, *args, **kwargs):
room = self.room_id
if self.guests > room.max_guests:
raise ValidationError("Assigned guests exceeding related room's maximum limit of {}"\
.format(room.max_guests))
super().save(*args, **kwargs)

How to implement class methods with attributes of another class?

I have three models, BoletoConfig(BilletConfig),Tickets and CategoryTicket, one ticket has a CategoryTicket and CategoryTicket has a BilletConfig, BilletConfig has an attribute with the days for the billet due, I want to create a method in the tickets class to calculate the due date.
I have doubts if I use the decorator #property or #classmethod, which would be the best choice and why? and how would i get the value of days_to_become_due from my BoletoConfig class in tickets?
This my /billets/models.BoletoConfig
class BoletoConfig(models.Model):
base_amount = models.DecimalField(
db_column='valor_base',
max_digits=10,
decimal_places=2,
verbose_name='valor base do boleto',
)
check_specialization_for_amount = models.BooleanField(
default=False,
db_column='especializacao_protocolo',
verbose_name='especialização do protocolo',
)
days_to_become_due = models.IntegerField(
db_column='dias_vencimento',
verbose_name='dias até vencimento',
)
class Meta:
db_table = 'boletos_boleto_configuracao'
verbose_name = 'boletos_configuração'
def __str__(self):
return self.base_amount
This my /tickets/models.tickets
class Ticket(TimestampedModel, permissions.TicketPermission):
"""A ticket requested by someone to address a situation"""
requested_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
models.PROTECT,
db_column='requerido_por',
related_name='+',
)
show_ticket_for_request_user = models.BooleanField(
default=True,
db_column='mostrar_ticket_para_requerente',
)
message = models.CharField(
max_length=4000,
blank=True,
null=True,
db_column='mensagem',
)
status = models.ForeignKey(
Status,
models.PROTECT,
)
category = models.ForeignKey(
Category,
models.PROTECT,
)
files = models.CharField(max_length=4000, blank=True, null=True)
boleto_id = models.UUIDField(
db_column='boleto_id',
verbose_name='Uuid Boleto',
null=True,
blank=True,
)
#classmethod
def get_boleto_duo_date(self, category):
days_to_due = 'category__boleto_config__days_to_become_due'
return self.ticket.created_at + timedelta(days=days_to_due)
This my /tickets/models.CategoryTicket
class Category(TimestampedModel):
"""
Represents what the ticket is about.
EX: Change lesson attendance, change activity grade.
"""
.
.
.
.
boleto_config = models.ForeignKey(
BoletoConfig,
models.PROTECT,
db_column='boleto_config_id',
verbose_name='configuração de boleto',
null=True,
blank=True,
)

How to expose some specific fields of model_b based on a field of model_a?

I want to create a ModelForm which gonna show some specific field of ControlInstruction if device_type of Device is equals DC. Otherwise show all fields.
Suppose,
if device type == 'DC':
show these filed in form-> on_off_flag, speed_flag, direction_flag
else:
show all
How can I do that?
class Device(models.Model):
DEVICE_TYPES = (
('AC', 'AC MOTOR'),
('DC', 'DC MOTOR'),
)
user = models.ForeignKey(User, on_delete=models.CASCADE)
device_id = models.CharField(max_length=64, unique=True, blank=False)
device_name = models.CharField(max_length=100, blank=False)
device_model = models.CharField(max_length=10)
device_type = models.CharField(max_length=2, choices=DEVICE_TYPES, blank=False)
location = models.CharField(max_length=150)
def __str__(self):
return self.device_name
class ControlInstruction(models.Model):
DIRECTION_CHOICES = (
('FW', 'Forward'),
('BW', 'Backward'),
)
# OneToOneField is is similar to a ForeignKey with unique=True, but the “reverse”
# side of the relation will directly return a single object.
device = models.OneToOneField(Device, on_delete=models.CASCADE, primary_key=True)
on_off_flag = models.BooleanField(default=False)
voltage_flag = models.FloatField(max_length=20, default=0)
current_flag = models.FloatField(max_length=20, default=0)
speed_flag = models.IntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(100)])
direction_flag = models.CharField(max_length=2, choices=DIRECTION_CHOICES, default='FW')
frequency_flag = models.IntegerField(default=0)
I would recommend creating two forms, one including only the fields for a DC device, and one form with all of the fields. Then in your view, choose which form to use based on the device_type.
class DeviceForm(forms.ModelForm):
class Meta:
model = Device
fields = "__all__"
def __init__(self,*args,**kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk:
if self.instance.device_type != "DC":
del self.fields["on_off_flag"]
del self.fields["speed_flag"]
del self.fields["direction_flag"]
But I dont recommended since you will find that this approach is very limited

Using one form for two similar models

I have the following models:
class BaseAddress(models.Model):
name = models.CharField(max_length=100)
business_name = models.CharField(max_length=100, blank=True, null=True)
address = models.CharField(max_length=100, blank=True, null=True)
address_2 = models.CharField(max_length=100, blank=True, null=True)
address_3 = models.CharField(max_length=100, blank=True, null=True)
city = models.CharField(max_length=100, blank=True, null=True)
state = models.CharField(max_length=2, blank=True, null=True)
zip_code = models.CharField(max_length=10, blank=True, null=True)
phone = models.CharField(max_length=30, blank=True, null=True)
class Meta:
abstract = True
class ProfileBilling(BaseAddress):
profile = models.OneToOneField(
Profile, related_name='billing_info')
class OrderBilling(BaseAddress):
order = models.OneToOneField(Order, related_name='billing')
name_on_card = models.CharField(max_length=100)
#card_type = models.PositiveSmallIntegerField(
# choices=CARD_TYPE, default=0)
#card_number = models.CharField(
# max_length=16, default=0)
expire_month = models.PositiveSmallIntegerField(
choices=MONTHS, null=True, default=0)
expire_year = models.PositiveSmallIntegerField(
choices=YEARS, null=True, default=1960)
When customers input a billing address, I want to save it in OrderBilling, but I also want to save it in ProfileBilling as their most recent billing address. How do I do so?
How do I go about using forms to save billing address in two different tables when the OrderBilling and ProfileBilling have most of the same fields...?
How do I do this in Django?
Here is my OrderBilling form:
class OrderBillingForm(forms.ModelForm):
class Meta:
model = OrderBilling
exclude = ('order',)
def __init__(self, *args, **kwargs):
super(OrderBillingForm, self).__init__(*args, **kwargs)
self.fields['address'].required = True
self.fields['city'].required = True
self.fields['state'] = USStateField()
self.fields['zip_code'] = us.USZipCodeField()
self.fields['phone'].required = False
self.fields['expire_month'].required = False
self.fields['expire_year'].required = False
def clean(self):
return self.cleaned_data
You can override save() method, But the smarter way in your case would be using post_save signal for this purpose.
After aOrderBilling get saved, You can save its data into ProfileBilling too.
look at some example on google search in case to be familiar with post_save signal,
like:
https://groups.google.com/forum/?fromgroups=#!topic/django-users/2m88qTrqnM8
http://www.djangofoo.com/85/signal-connect-disconnect-django-signals
etc...
then Easyily in your post_save receiver|callback funcstion get the OrderBilling instance
orderbil_instance = kwargs['instance']
And create your ProfileBilling from its data
ProfileBilling.objects.create(name=orderbil_instance.name, ....)
Something like on post_save signal receiver
def do_something(sender, **kwargs):
# Getting OrderBilling instance which get saved just now
orderbil_instance = kwargs['instance']
# Save the data into new ProfileBilling
ProfileBilling.objects.create(name=orderbil_instance.name, ....)

Categories