The idea of the code below should be that it only fires if the field verification_pin is empty i.e. on a new record. However, it seems that every time I save the model it generates a new pin ignoring if instance.verification_pin is None statement, why, what have I missed?
#receiver(pre_save, sender=CompanyUser)
def my_callback(sender, instance, *args, **kwargs):
if instance.verification_pin is None:
instance.verification_pin = instance.generate_pin()
instance.is_active = False
instance.send_verification_pin()
Model:
class CompanyUser(User):
verification_pin = models.IntegerField(max_length=4, null=True)
objects = UserManager()
def generate_pin(self):
"""
Returns a random four digit pin.
"""
return random.randint(999, 9999)
def send_verification_pin(self):
self.email_user(
subject="Test",
message="Your pin: %s" % self.verification_pin,
from_email=settings.DEFAULT_FROM_EMAIL
)
You can set the default value for a field to a callable object.
Or, you might try using a post_save handler instead, where you can check if created is True. Also, it might be helpful to check the value in verification_pin after saving, to see if it really got set or not.
Related
Im not getting why this signal is not working. This same code worked once but after that i deleted the objects from admin and ran it again and it stopped working.
#receiver(post_save, sender=FinancePending)
def calcualate_FinancePending(sender, instance, created, **kwargs):
amount_paid = FinancePending.objects.values_list('amountPaid', flat=True)
amount_paid = list(amount_paid)
total_amount = FinancePending.objects.values_list('TotalAmount', flat=True)
total_amount = list(total_amount)
# total - paid
TotalFee = [int(s.replace(',', '')) for s in total_amount]
AmountPaid = [int(s.replace(',', '')) for s in amount_paid]
finance_pending = FinancePending.objects.all()
i = 1
while i <= len(TotalFee):
amount_pending = TotalFee[i-1] - AmountPaid[i-1]
amountpending = FinancePending.objects.filter(invoiceNumber=i)
amountpending.AmountPending = amount_pending
i = 1 + i
You did not call save() method, that is why it is not saving. But I do not think it is a optimized implementation from django's prespective. You can simply try like this using update():
from django.db.models import F
#receiver(post_save, sender=FinancePending)
def calcualate_FinancePending(sender, instance, created, **kwargs):
FinancePending.objects.update(AmountPending=F('TotalAmount')-F('amountPaid'))
Also, it does not make sense to update each and every object one instance of FinancePending is created. Probably you should only update the object which was created. Like this:
#receiver(post_save, sender=FinancePending)
def calcualate_FinancePending(sender, instance, created, **kwargs):
instance.AmountPending=instance.TotalAmount-instance.amountPaid
instance.save()
Finally, please follow pep8 style guide when naming your attributes and function names.
Because signal post_save only triggered after call save() method. You should use post_delete instead.
I am creating a browser-based RPG where fighting mechanics are built into a model called "Battle". It performs actions on Hero, Monster and Item models according to some formulas. Each action adds a message to a "battle log". A player can issue a fight against another player or NPC in a form. When the form is submitted, it calls the same view, the Battle object is created, the characters are drafted and the game mechanics are run.
For some reason, old "Battle" objects are still "selected" between runs of these views, as long as it's in the same web session. So even though I create a new object, the old battle log gets carried over to this new object.
What am I doing wrong here?
Updated with more context
The fightlog field in the first object is correct.
The fightlog field in the second object is the the first objects data PLUS the new data.
The third fightlog is the first plus second plus third, etc.
views.py
def battle_log(request):
if request.session["last_battle"]:
pk = request.session["last_battle"]
b = Battletest.objects.get(pk=pk)
battle_log = b.fightlog
request.session["last_battle"] = ""
context = { 'battle_log' : battle_log, }
return render(request, 'battle.html', context)
else:
return redirect('/game/monster')
def fight_select_monster(request):
form = SelectCharacter()
if request.method=='POST':
form = SelectCharacter(request.POST)
if form.is_valid():
b = Battletest.objects.create()
b.draft(request.POST.get("character"))
b.start_fight()
b.round()
b.eof()
b.save()
request.session["last_battle"] = b.pk
return redirect('/game/fight/')
context = { 'form': form }
request.session["last_battle"] = ""
return render(request, 'fight.html', context)
models.py
class Battletest(models.Model):
messages = []
fightlog = models.TextField()
opponent = ""
def draft(self, opponent):
CHARACTERS= (
(0, 'Confident Hacker'),
(1, 'Confused Coder'),
)
self.opponent = CHARACTERS[int(opponent)][1]
def start_fight(self):
self.messages.append([0, "You joined the battle."])
self.messages.append([0, self.opponent + "has appeared"])
def round(self):
# have character objects do stuff to eachother until
# some edge case is met.
self.messages.append([1, "You smack " + self.opponent + " in the face"])
self.messages.append([1, self.opponent + " decides to leave this stupid fight"])
def eof(self):
self.messages.append([2, "The fight is over and noone wins"])
self.fightlog = self.messages
forms.py
class SelectCharacter(forms.Form):
CONFIDENTHACKER = 0
CONFUSEDCODER = 1
CHARACTERS= (
(CONFIDENTHACKER, 'Confident Hacker'),
(CONFUSEDCODER, 'Confused Coder'),
)
character = forms.ChoiceField(choices=CHARACTERS)
Ok, your issue is here:
class Battletest(models.Model):
messages = []
opponent = ""
This defines messages and opponent as class attributes
- attributes that belong to the class object itself and as such are shared between all instances of the class, making them, practically, global variables (since class objects are singletons).
What you want here is to make messages an instance attribute by defining int in the initializer (that's what it's for):
class Battletest(models.Model):
fightlog = models.TextField()
def __init__(self, *args, **kwargs):
# let Model do it's own stuff !!!
super(Battletest, self).__init__(*args, **kwargs)
# and add our ones:
self.messages = []
self.opponent = None
As a side note: such mistakes are often the sign someone kind of "jumped in" Django without learning Python's basics first and wrongly assumes that because Django models use class attribute to define db fields, Python's class syntax is the same as Java or PHP where you define attributes at the class top-level. But that's not how Python works and I very strongly suggest that at this point you stop everything and do the whole official Python tutorial - it will saves you a lot of time and pain, really.
As a second side note: in the context of server side web app, you want to avoid any kind of (mutable) global state in your code. Every bit of mutable global state should live in some databaseyour models, sessions, whatever as long as it's external to your code AND can be shared amongst many processes - because in a typical production setup, your app WILL be served by many distinct processes (yes, even if you have one single HTTP front server, it will typically manage a pool of django processes, and requests will arbitrarily dispatched to any of those processes).
Now, you have another issue here:
def eof(self):
# ...
self.fightlog = self.messages
You defined fightlog as a text field, but you're assigning a list of lists to it. What get saved will be a textual representation of the list, which is not very usable.
Theoretically, what you have here is a one to many relationship (a Battletest has many Message), so the proper relational design would be to use a distinct Message model with a ForeignKey on Battletest - as shown in the tutorial (you did the tutorial, did you ?).
Now if you really insist on denormalizing this, the best (less worse at least) solution is to serialize messages to json at save() time and unserialize it back to Python in the initializer. This can be done manually:
import json
class Battletest(models.Model):
fightlog = models.TextField()
def __init__(self, *args, **kwargs):
# let Model do it's own stuff !!!
super(Battletest, self).__init__(*args, **kwargs)
# and add our ones:
if self.fightlog:
self.messages = json.loads(self.fightlog)
else:
self.messages = []
self.opponent = None
# ...
def save(self, *args, **kwargs):
self.fightlog = json.dumps(self.messages)
super(Battletest, self).save(*args, **kwargs)
or using a JSONField (that will more or less automagically take care of this) if your RDBMS support it. Googling for "django JSONField" should provide some hints...
Oh and yes... you have duplicated code here:
class Battletest(models.Model):
# ...
def draft(self, opponent):
CHARACTERS= (
(0, 'Confident Hacker'),
(1, 'Confused Coder'),
)
self.opponent = CHARACTERS[int(opponent)][1]
and here:
class SelectCharacter(forms.Form):
CONFIDENTHACKER = 0
CONFUSEDCODER = 1
CHARACTERS= (
(CONFIDENTHACKER, 'Confident Hacker'),
(CONFUSEDCODER, 'Confused Coder'),
)
character = forms.ChoiceField(choices=CHARACTERS)
You want to factor this out so you have one single point of truth:
class Battletest(models.Model):
CONFIDENTHACKER = 0
CONFUSEDCODER = 1
CHARACTERS= [
(CONFIDENTHACKER, 'Confident Hacker'),
(CONFUSEDCODER, 'Confused Coder'),
]
def draft(self, opponent):
self.opponent = self.CHARACTERS[int(opponent)][1]
and in your forms.py:
from . models import Battletest
class SelectCharacter(forms.Form):
character = forms.ChoiceField(choices=Battltest.CHARACTERS)
I want to log every action what will be done with some SQLAlchemy-Models.
So, I have a after_insert, after_delete and before_update hooks, where I will save previous and current representation of model,
def keep_logs(cls):
#event.listens_for(cls, 'after_delete')
def after_delete_trigger(mapper, connection, target):
pass
#event.listens_for(cls, 'after_insert')
def after_insert_trigger(mapper, connection, target):
pass
#event.listens_for(cls, 'before_update')
def before_update_trigger(mapper, connection, target):
prev = cls.query.filter_by(id=target.id).one()
# comparing previous and current model
MODELS_TO_LOGGING = (
User,
)
for cls in MODELS_TO_LOGGING:
keep_logs(cls)
But there is one problem: when I'm trying to find model in before_update hook, SQLA returns modified (dirty) version.
How can I get previous version of model before updating it?
Is there a different way to keep model changes?
Thanks!
SQLAlchemy tracks the changes to each attribute. You don't need to (and shouldn't) query the instance again in the event. Additionally, the event is triggered for any instance that has been modified, even if that modification will not change any data. Loop over each column, checking if it has been modified, and store any new values.
#event.listens_for(cls, 'before_update')
def before_update(mapper, connection, target):
state = db.inspect(target)
changes = {}
for attr in state.attrs:
hist = attr.load_history()
if not hist.has_changes():
continue
# hist.deleted holds old value
# hist.added holds new value
changes[attr.key] = hist.added
# now changes map keys to new values
I had a similar problem but wanted to be able to keep track of the deltas as changes are made to sqlalchemy models instead of just the new values. I wrote this slight extension to davidism's answer to do that along with slightly better handling of before and after, since they are lists sometimes or empty tuples other times:
from sqlalchemy import inspect
def get_model_changes(model):
"""
Return a dictionary containing changes made to the model since it was
fetched from the database.
The dictionary is of the form {'property_name': [old_value, new_value]}
Example:
user = get_user_by_id(420)
>>> '<User id=402 email="business_email#gmail.com">'
get_model_changes(user)
>>> {}
user.email = 'new_email#who-dis.biz'
get_model_changes(user)
>>> {'email': ['business_email#gmail.com', 'new_email#who-dis.biz']}
"""
state = inspect(model)
changes = {}
for attr in state.attrs:
hist = state.get_history(attr.key, True)
if not hist.has_changes():
continue
old_value = hist.deleted[0] if hist.deleted else None
new_value = hist.added[0] if hist.added else None
changes[attr.key] = [old_value, new_value]
return changes
def has_model_changed(model):
"""
Return True if there are any unsaved changes on the model.
"""
return bool(get_model_changes(model))
If an attribute is expired (which sessions do by default on commit) the old value is not available unless it was loaded before being changed. You can see this with the inspection.
state = inspect(entity)
session.commit()
state.attrs.my_attribute.history # History(added=None, unchanged=None, deleted=None)
# Load history manually
state.attrs.my_attribute.load_history()
state.attrs.my_attribute.history # History(added=(), unchanged=['my_value'], deleted=())
In order for attributes to stay loaded you can not expire entities by settings expire_on_commit to False on the session.
In order to make a simple captacha-like field, I tried the following:
class CaptchaField(IntegerField):
def __init__(self, *args, **kwargs):
super(CaptchaField, self).__init__(*args, **kwargs)
self.reset()
def reset(self):
self.int_1 = random.randint(1, 10)
self.int_2 = random.randint(1, 10)
self.label = '{0} + {1}'.format(self.int_1, self.int_2)
def clean(self, value):
value = super(CaptchaField, self).clean(value)
if value != self.int_1 + self.int_2:
self.reset()
raise ValidationError(_("Enter the result"), code='captcha_fail')
return True
Every time my answer is wrong, the label is changed as expected but the test is performed against the first values of int_1 and int_2 and not against the newly randomly generated values.
I don't understand how Field object are created and why I can't access the values of my field.
Thanks in advance
Have a think about how this works in your view. When you render the form, the field is instantiated and sets the label to your random values, which is fine. Now, the user posts back to the view: what happens? Well, the form is instantiated again, as is the field, and the field is set to two new random values. Not surprisingly, this won't match up to the previous value, because you haven't stored that anywhere.
To do anything like this, you need to store state somewhere so it is preserved between requests. You could try putting it in the session, perhaps: or, a better way might be to hash the two values together and put them in a hidden field, then on submit hash the submitted value and compare it against the one in the hidden field. This would probably need to managed at the form level, not the field.
I'm trying to create a custom timestamp field.
class TimestampKey(models.CharField):
__metaclass__ = models.SubfieldBase
def __init__(self, *args, **kwargs):
import time
kwargs['unique'] = True
kwargs['max_length'] = 20
kwargs['auto_created'] = True
kwargs['editable']=False
super(TimestampKey, self).__init__(*args, **kwargs)
def to_python(self, value) :
return value
def get_db_prep_value(self, value) :
try:
import time
t = time.localtime()
value = reduce(lambda a,b:str(a)+str(b),t)
except ValueError:
value = {}
return value
class Table1(models.Model):
f = TimestampKey(primary_key=True)
n = ....
It stores the value with appropriate timestamp in the db. But it doesnt populate the field 'f' in the object.
Eg:
t1 = Table1(n="some value")
t1.f -> blank
t1.save()
t1.f -> blank.
This is the problem. Am I missing something so that it doesnt populate the filed?
Please shed some light on this.
Thanks.
Is it wise to use a timestamp as your primary key? If your database uses ISO 8601 or really any time format in which second is the smallest time interval... Well, anyway, my point is that you have no guarantee, especially if this is going to be a web-facing application that two entries are going to resolve within the minimum time interval. That is, if the smallest time interval is a second, as in ISO 8601, if you get two requests to save in the same second, you're going to get an error condition. Why not stick to automatically incrementing integer keys and just make the timestamp its own field?
The get_db_prep_value method only prepares a value for the database, but doesn't send the prepared value back to the Python object in any way. For that you would need the pre_save method, I think.
Fortunately, there's already an "auto_now" option on DateField and DateTimeField that does what you want, using pre_save. Try:
class Table1(models.Model):
f = models.DateTimeField(auto_now=True)
(If you must write your own pre_save, look at how auto_now modifies the actual model instance in /django/db/models/fields/__init__.py on lines 486-492:
def pre_save(self, model_instance, add):
if self.auto_now or (self.auto_now_add and add):
value = datetime.datetime.now()
setattr(model_instance, self.attname, value)
return value
else:
return super(DateField, self).pre_save(model_instance, add)
)