How can I write a context manager that, in test/dev, asserts that there are no database queries in its block? The goal here is to force usage of select_related/prefetch related, and make it an error to forget to do so. Eg, given these models:
class SomeSingleThing(models.Model):
somefield = models.IntegerField()
someotherfield = models.CharField(max_length=42)
class SomeManyThing(models.Model):
parent = models.ForeignKey(SomeSingleThing)
This should raise an exception, because select_related is missing, and so a query gets performed inside the forbidden zone:
obj = SomeSingleThing.objects.get(pk=1)
with forbid_queries():
val = obj.parent.somefield
# => exception
And this should not, because we selected everything we needed:
obj = SomeSingleThing.objects.select_related('parent').get(pk=1)
with forbid_queries():
val = obj.parent.somefield
# no exception
Related
I've got a question about transactions in flask-sqlalchemy.
def funca():
instance = MyModel.query.get(5)
try:
instance.some_field = 'test'
instance.do_something()
instance.do_something_other() # Let's pretend that this causes exception
db.session.commit()
except Exception:
db.session.rollback()
# My model method
def do_something(self):
external_id = self.make_request_to_external_service()
self.external_id = external_id
# My model method
def do_something_other(self):
external_details = self.make_another_external_request()
self.external_details = external_details
How can I achieve now, that on Exception this will rollback only some_field and external_details changes and external_id is still saved in db?
Can I use nested transaction or can I start manually independent transactions for these 3 operations?
Problem here is that do_something will get external_id and it must be saved in db, because I can't do it second time, also if I won't rollback this transaction some_field will cause desynchronization between my db and external service.
For this example's purposes let's pretend that I can't do do_something before instance.some_field = 'test', because I could run this and commit this independently, but I can't do that in my real life app.
I am implementing a field in django model which calls the following function for default value
def get_default_value():
a = MyModel.objects.aggregate(max_id=Max('id'))
return get_unique_value_based_on_num(a['max_id'] or 0)
class MyModel:
default_value_field = CharField(default=get_default_value)
Although I may be wrong on this, I fear this implementation may result in race condition.
Is there a better way to do this ? May be use F object or something else ?
To avoid race conditions, it is best letting the database handle the integrity of your table, which is one of the things databases are made for.
To do so, catch any IntegrityError raised by saving your model instance and try again with a different value when it fails.
from django.db import IntegrityError, models, transaction
def get_default_value():
a = MyModel.objects.aggregate(max_id=Max('id'))
return get_unique_value_based_on_num(a['max_id'] or 0)
class MyModel(models.Model):
# Have unicity enforced at database level with unique=True.
default_value_field = models.CharField(max_length=200, unique=True)
def save(self):
if not self.default_value_field:
max_tries = 100 # Choose a sensible value!
for i in range(max_tries):
try:
self.default_value_field = get_default_value()
# Atomic block to rollback transaction in case of IntegrityError.
with transaction.atomic():
super(MyModel, self).save()
break
except IntegrityError:
# default_value_field is not unique, try again with a new value.
continue
else:
# Max tries reached, raise.
raise IntegrityError('Could not save model because etc...')
else:
super(MyModel, self).save(*args, **kwargs)
Seems like django has select_for_update for queryset API. It may be the solution to my problem.
https://docs.djangoproject.com/en/dev/ref/models/querysets/#select-for-update
I wrote a view to update my draft object , before updating my draft I need to see if any draft exists for package(draft.package) in db or not .
If any draft available, i need to update that draft's fields.
I am using get queryset to look into db to check draft availability.
I want to know that using get queryset here is good way or not and using pass into except.
My View
def save_draft(draft, document_list):
"""
"""
try:
draft = Draft.objects.get(package=draft.package)
except Draft.DoesNotExist as exc:
pass
except Draft.MultipleObjectsReturned as exc:
raise CustomException
else:
draft.draft_document_list.filter().delete()
draft.draft_document_list.add(*document_list)
draft.save()
Extra Information :
models.py
class Package(models.Model):
name = models.CharField(max_length=100)
# -- fields
class Document(models.Model):
# -- fields
Class Draft(models.Model):
# --- fields
package = models.ForeignKey(Package)
draft_document_list = models.ManyToManyField(Document)
My Algorithm :
# first check to see if draft exists for package
# if exists
# overwrite draft_document_list with existed draft and save
# if none exists
# update passed draft object with draft_document_list
Input variables
save_draft(draft, document_list)
draft --> latest draft object
document_list --> list of documents mapped with Draft as M2M.
Yes, for for you models and method signature you use get right. To simplify things you can get rid of delete()/add() methods by direct assign document_list to M2M relation.
def save_draft(draft, document_list):
try:
draft = Draft.objects.get(package=draft.package)
except Draft.DoesNotExist:
pass
except Draft.MultipleObjectsReturned:
raise CustomException
draft.draft_document_list = document_list
draft.save()
EDIT: If there can be only one draft per package then why you use ForeignKey(Package)? With OneToOne relation your code will be much simpler:
def save_draft(draft, document_list):
draft.draft_document_list = document_list
draft.save()
I'm using Flask and flask-SQLAlchemy extension to make a webapp and I've added fields like unique=True in the model declarations.
class Coupon(db.Model):
username = db.Column(db.String(80), primary_key=True)
value = db.Column(db.String(80), unique=True)
is_valid = db.Column(db.Boolean)
def __init__(self, value, username):
self.value = value
self.username = username
self.is_valid = True
What is the best(pythonic) way of recovering with a failed validation. For example -
c1 = Coupon("same_value", "foo")
db.session.add(c1)
c2 = Coupon("same_value", "bar")
db.session.add(c2)
db.session.commit() #gives an IntegrityError
Should I use a try, except block to deal with this? Or is there a better way to deal with transcations that have not succeeded. One additional query regarding transactions - As long as keep adding objects to a session and not do a session.commit is it all a part of one transcation?
Thanks
I am not sure if it makes sense to try-catch the commit() call. Even if you do, how do you proceed further? How do you further locate the problem and fix it? You might have dozens of duplicates already.
The motto here is: CATCH ERRORS EARLY.
Therefore, what I would probably do is: I would add a call to session.flush() just after the session.add(c?). This will indicate the problem early enough for me to handle properly. So it could make more sense to wrap the add/flush in the try-catch block and handle as appropriate:
def _add_coupon(c):
""" #param c: instance of Coupon.
#return: True if success, False on failure.
"""
try:
session.add(c)
session.flush()
return True
except sqlalchemy.exc.IntegrityError as err:
# #todo: handle as appropriate: return existing instance [session.query(Coupon).filter(Coupon.value==c.value).one()] or re-raise
logger.error("Tried to add a duplicate entry for the Coupon value [%s]. Aborting", c.value)
return False
I have searched around for an answer to this but can't find one. When using a ForeignKey, I am consistently getting an error telling me that 'Foo object has no attribute 'foo_set'. I am a bit new to Django/Python, so I'm sure there is a simple answer here, but I haven't been able to find it so far. Here's some code (to store varied Boards for use in a game, each of which should have a number of Hexes associated with it):
Models:
class Boards(models.Model):
boardnum = models.IntegerField(unique=True)
boardsize = models.IntegerField(default=11)
hexside = models.IntegerField(default=25)
datecreated = models.DateTimeField(auto_now_add = True)
class Hexes(models.Model):
boardnum = models.ForeignKey(Boards, null = True)
col = models.IntegerField()
row = models.IntegerField()
cost = models.IntegerField(default=1)
Code (this works):
newboard, createb = Boards.objects.get_or_create(boardnum=boardn)
createb returns True.
Code (this immediately follows the above, and does not work):
try:
hx = newboard.boards_set.create(col=c, row=r)
except Exception, err:
print "error:", err
traceback.print_exc()
Both "err" and "traceback.print_exc()" give: AttributeError: 'Boards' object has no attribute 'boards_set'
I get the same error if I first create the Hexes record with a get_or_create and then try a newboard.boards_set.add() on it.
Any ideas? All suggestions appreciated.
The name that Django uses for a reverse foreign key manager is the name of the model that contains the foreign key, not the name of the model that the manager is on.
In your case, it will be:
newboard.hexes_set.create(col=c,row=r)
I find it useful to use the manage.py shell command to import your models and inspect them (with dir, etc) to check out all the available attributes.