Django jsonfield persisting old object values in a loop - python

Here is the model:
class ModelA(models.Model):
field1 = models.CharField(max_length=100)
field2 = models.CharField(max_length=100)
field3 = JSONField(default=[])
def save(self, *args, **kwargs):
# Below print should be None
# But it shows the value of the previously created object, why ?
print "------------------------------------------------"
print self.id
print "self.field3"
print self.field3
self.field2 = self.field1 + " world"
self.field3.append(self.field2)
super(ModelA, self).save(*args, **kwargs)
Here is the view:
def view1(request):
for x in range(1, 3):
a = ModelA.objects.create(field1="hello%s" % x)
Expected output:
# None
# self.field3
# []
# None
# self.field3
# []
Achieved output:
# None
# self.field3
# []
# None
# self.field3
# [u'Hello1 world']
# None
# self.field3
# [u'Hello1 world', u'Hello2 world']
So, as per the given output, can you tell me why its using previous objects values while creating a new object ?

This is from Django documentation:
If you give the field a default, ensure it’s a callable such as dict
(for an empty default) or a callable that returns a dict (such as a
function). Incorrectly using default={} creates a mutable default that
is shared between all instances of JSONField.
Therefore use this instead:
field3 = JSONField(default=list)

Related

Class object not returning value

class Friend:
all = []
def __init__(self):
self.__fname = None
self.__lname = None
self.__fid = None
#property
def fname(self):
return self.__fname
#fname.setter
def fname(self, value):
self.__fname = value
#property
def lname(self):
return self.__lname
#lname.setter
def lname(self, value):
self.__lname = value
#property
def fid(self):
return self.__fid
#fid.setter
def fid(self, value):
self.__fid = value
#DB Class
class db_friend()
def db_load_friend(self, obj, fname,lname):
obj.fname = fname
obj.lname = lname
obj.fid = "XYZ"
obj.all.append(obj)
# function that acts on the friend class
def manage_friend():
fname = "Joe"
lname = "Root"
objfriend = Friend()
db_friend.db_load_friend(objfriend, fname,lname)
print (objfriend.fname) # this is not working
print (objfriend.fid) #this is not working
for user in objfriend.all:
print (objfriend.fid) #this is working
Both objfriend.fname and objfriend.fid is printing no value. I am trying to load the objfriend object by passing to the db_load_friend method of the db class. I am able to see the values if I loop through the "all" variable. May I know why this is not working or using the static variable "all" is the only way to do it?
You need to create an instance of db_friend so you can call the db_load_friend() method:
def manage_friend():
fname = "Joe"
lname = "Root"
objfriend = Friend()
objdbfriend = db_friend()
objdbfriend.db_load_friend(objfriend, fname,lname)
print (objfriend.fname)
print (objfriend.fid)
for user in objfriend.all:
print (objfriend.fid) #this is working
Or, since db_load_friend() doesn't need to use self, you could make it a static method.
class db_friend()
#staticmethod
def db_load_friend(obj, fname,lname):
obj.fname = fname
obj.lname = lname
obj.fid = "XYZ"
obj.all.append(obj)

Read MongoEngine DynamicDocuments

my issue is that I am saving dict objects with MongoEngine:
class MongoRecord(DynamicDocument):
record_id = SequenceField(primary_key = True)
class SimpleMongo(object):
def __init__(self, *args, **kwargs):
"""
Very simple dict-like Mongo interface
"""
if PY_VERSION == 2:
self.iterattr = 'iteritems'
else:
self.iterattr = 'items'
self.debug = DEBUG
self.dict_type = type(dict())
self.dbname = kwargs.get('dbname', 'untitled')
self.collection_name = kwargs.get('collection', 'default')
self.ip = kwargs.get('ip', '127.0.0.1')
self.port = kwargs.get('port', 27017)
self.dbconn = connect(self.dbname, host=self.ip, port=self.port)
drop = kwargs.get('drop', False)
if drop:
self.dbconn.drop_database(self.dbname)
def put(self, data):
"""
Put dict
"""
assert type(data) == self.dict_type
record = MongoRecord()
record.switch_collection(self.collection_name)
generator = getattr(data, self.iterattr)
__res__ = [setattr(record, k, v) for k,v in generator()] # iteritems() for Python 2.x
record.save()
but when trying to access them:
def get(self):
record = MongoRecord()
record.switch_collection(self.collection_name)
return record.objects
getting
mongoengine.queryset.manager.QuerySetManager object, not an iterator.
So, what is the proper way to get my data back from Mongo being saved as DynamicDocument?
The problem isn't that MongoRecordis a DynamicDocument or that it contains a dict. You would get the same result with a regular Document. Your problem is with querying, you should change record.objects to MongoRecord.objects to get a cursor.
Regarding your usage of switch_collection()...
If MongoRecord documents will be saved to a collection with the same name, at most times, you can define this like below, and you don't have to use switch_collection() when a collection with that name is being queried.
class MongoRecord(DynamicDocument):
record_id = SequenceField(primary_key = True)
meta = {'collection': 'records'}
In case you do want to retrieve MongoRecord documents from a collection which isn't called 'records', and you want to define a function for this (which can give an UnboundLocalError), you can do it like this (source):
from mongoengine.queryset import QuerySet
def get(self):
new_group = MongoRecord.switch_collection(MongoRecord(), self.collection_name)
new_objects = QuerySet(MongoRecord, new_group._get_collection())
all = new_objects.all()
# If you would like to filter on an MongoRecord attribute:
filtered = new_objects.filter(record_id=1)
return all

How to avoid date overlaps in Django models?

I have a model:
class Dimension_Item(models.Model):
uq_dimension_item_id = MyCharField(max_length=1024, primary_key=True)
dimension_id = MyCharField(max_length=1024)
dimension_item_id = MyCharField(max_length=100)
name = MyCharField(max_length=255)
description = MyCharField(max_length=512, null = True)
start_date = models.DateField(default=date(1,1,1))
end_date = models.DateField(default=date(9999,12,31))
function for adding information to the model:
def addRows(in_args):
rq = in_args[0]
pk_l=[]
rows = rq['rows']
if type(rows).__name__ == 'dict':
dim = Dimension_Item(
name=rows['name'],
start_date=rows['start_date'],
end_date=rows['end_date']
)
dim.save()
pk_l.append(dim.dimension_id)
elif type(rows).__name__ == 'list':
for i in rows:
dim = Dimension_Item(
name=rows['name'],
start_date=rows['start_date'],
end_date=rows['end_date']
)
dim.save()
pk_l.append(dim.dimension_id)
else:
pass
return getRows(in_args, pk_l)
# return "success add row"
clauses.addMethod(addRows)
and function for modifying the model items:
def modifyRows(in_args):
pk_l=[]
rq = in_args[0]
rows = rq['rows']
if type(rows).__name__ == 'dict':
dim = Dimension_Item.objects.get(pk=rows['pk'])
for attr in rows:
if attr!='pk':
try:
setattr(dim, attr, rows[attr])
except KeyError:
pass
dim.save()
pk_l.append(dim.dimension_id)
elif type(rows).__name__ == 'list':
for i in rows:
dim = Dimension_Item.objects.get(pk=i['pk'])
for attr in i:
if i!='pk':
try:
setattr(dim, attr, i[attr])
except KeyError:
pass
dim.save()
pk_l.append(dim.dimension_id)
else:
pass
return getRows(in_args, pk_l)
# return "success modify"
clauses.addMethod(modifyRows)
I should check if start_date and end_date fields don't overlap other records in the database.
For example, I enter: 2.02.1988-3.07.1989. And if I already have the record with 2.07.1989 - 3.08.1990, I have to throw an exception about date overlapping.
How better can I do it?
I would override the save() method of your Dimension_Item model.
In your custom save() method you can implement your check for overlapping dates. If everything is OK, create the object. If not, just return nothing (or raise and error.)
The Django documentation explains it really good: https://docs.djangoproject.com/en/dev/topics/db/models/#overriding-model-methods
Here is some (untested) code to get you started:
class Dimension_Item(models.Model):
start_date = models.DateField(default=date(1,1,1))
end_date = models.DateField(default=date(9999,12,31))
def save(self, *args, **kwargs):
# get number of items that have an overlapping start date
dimension_items_overlapping_start = Dimension_Item.objects.filter(start_date__gte=self.start_date, start_date__lte=self.end_date).count()
# get number of items that have an overlapping end date
dimension_items_overlapping_end = Dimension_Item.objects.filter(end_date__gte=self.start_date, end_date__lte=self.end_date).count()
overlapping_dimension_items_present = dimension_items_overlapping_start > 0 or dimension_items_overlapping_end > 0
if overlapping_dimension_items_present:
return
else:
super(Dimension_Item, self).save(*args, **kwargs) # Call the "real" save() method.
class Dimension_Item(models.Model):
start_date = models.DateField(default=date(1,1,1))
end_date = models.DateField(default=date(9999,12,31))
def save(self, *args, **kwargs):
# check for items that have an overlapping start date
dimension_items_overlapping_start = Dimension_Item.objects.filter(start_date__gte=self.start_date, start_date__lte=self.end_date).exists()
# check for items that have an overlapping end date
dimension_items_overlapping_end = Dimension_Item.objects.filter(end_date__gte=self.start_date, end_date__lte=self.end_date).exists()
# check for items that envelope this item
dimension_items_enveloping = Dimension_Item.objects.filter(start_date__lte=self.start_date, end_date__gte=self.end_date).exists()
overlapping_dimension_items_present = dimension_items_overlapping_start or dimension_items_overlapping_end or dimenstion_items_enveloping
if overlapping_dimension_items_present:
return
else:
super(Dimension_Item, self).save(*args, **kwargs) # Call the "real" save() method.

Class instances overwrite attributes

I've written a class in Python for calculations, it looks like:
default = {…}
class Case(object):
def __init__(self, name, wt, wd, ft, bc, burnup, cr_state):
self.name = name
self.burnup = burnup
self.infn = 'fa-'+faType+'-'+str(self.burnup)+'-'+self.name
self.data = default
self.data['caseName'] = name
self.data['water-temp'] = str(wt)
self.data['water-den'] = str(wd)
self.data['fuel-temp'] = str(ft)
self.data['boron-con'] = str(bc)
self.cr_state = cr_state
self.data['cr_state'] = cr_state
self.data['burnup'] = str(burnup)
Actually it implements more methods, but this should be enough just to illustrate.
The problem is that when I'm trying to create different instances of this class they turn out to have same attributes, like:
basis = Case('basis', 578.0, 0.71614, 578.0, 0.00105, 0, 'empty')
wt450 = Case('wt450', 450.0, 0.71614, 578.0, 0.00105, 0, 'empty')
and after this if I check:
print basis.data == wt450.data
it returns True. Where can the root of the problem be?
Like the comment from jonrsharpe states, you could copy the content of the default dict with the `dict.copy method.
class Case(object):
def __init__(self, name, wt, wd, ft, bc, burnup, cr_state):
self.name = name
self.burnup = burnup
self.infn = 'fa-'+faType+'-'+str(self.burnup)+'-'+self.name
# Copy the content of the dict
self.data = default.copy()
# Overwrite data's default values
self.data['caseName'] = name
self.data['water-temp'] = str(wt)
self.data['water-den'] = str(wd)
self.data['fuel-temp'] = str(ft)
self.data['boron-con'] = str(bc)
self.cr_state = cr_state
self.data['cr_state'] = cr_state
self.data['burnup'] = str(burnup)
Or, if you want to keep the values synchronized with the default ones, you could create a method that would take the value from the instance and if it can't find the value, then use the one on the default dict. It would look something like this:
class Case(object):
def __init__(self, name, wt, wd, ft, bc, burnup, cr_state):
self.name = name
self.burnup = burnup
self.infn = 'fa-'+faType+'-'+str(self.burnup)+'-'+self.name
# We create a NEW dictionary to hold the overwritten values
self.data = {}
# Write data to OUR OWN dict
self.data['caseName'] = name
self.data['water-temp'] = str(wt)
self.data['water-den'] = str(wd)
self.data['fuel-temp'] = str(ft)
self.data['boron-con'] = str(bc)
self.cr_state = cr_state
self.data['cr_state'] = cr_state
self.data['burnup'] = str(burnup)
def property(name):
"""
Return the value of `name`, if name wasn't defined, then use the
value from default.
"""
# Return the property from our own dict. If it can't find the property
# then get it from the default dict. If the property doesn't exists
# returns None.
return self.data.get(name, default.get(name))
# Then
print basis.property('caseName') == wt450.property('caseName')
> False

Django model class decorator

I have a need to track changes on Django model instances. I'm aware of solutions like django-reversion but they are overkill for my cause.
I had the idea to create a parameterized class decorator to fit this purpose. The arguments are the field names and a callback function. Here is the code I have at this time:
def audit_fields(fields, callback_fx):
def __init__(self, *args, **kwargs):
self.__old_init(*args, **kwargs)
self.__old_state = self.__get_state_helper()
def save(self, *args, **kwargs):
new_state = self.__get_state_helper()
for k,v in new_state.items():
if (self.__old_state[k] != v):
callback_fx(self, k, self.__old_state[k], v)
val = self.__old_save(*args, **kwargs)
self.__old_state = self.__get_state_helper()
return val
def __get_state_helper(self):
# make a list of field/values.
state_dict = dict()
for k,v in [(field.name, field.value_to_string(self)) for field in self._meta.fields if field.name in fields]:
state_dict[k] = v
return state_dict
def fx(clazz):
# Stash originals
clazz.__old_init = clazz.__init__
clazz.__old_save = clazz.save
# Override (and add helper)
clazz.__init__ = __init__
clazz.__get_state_helper = __get_state_helper
clazz.save = save
return clazz
return fx
And use it as follows (only relevant part):
#audit_fields(["status"], fx)
class Order(models.Model):
BASKET = "BASKET"
OPEN = "OPEN"
PAID = "PAID"
SHIPPED = "SHIPPED"
CANCELED = "CANCELED"
ORDER_STATES = ( (BASKET, 'BASKET'),
(OPEN, 'OPEN'),
(PAID, 'PAID'),
(SHIPPED, 'SHIPPED'),
(CANCELED, 'CANCELED') )
status = models.CharField(max_length=16, choices=ORDER_STATES, default=BASKET)
And test on the Django shell with:
from store.models import Order
o=Order()
o.status=Order.OPEN
o.save()
The error I receive then is:
TypeError: int() argument must be a string or a number, not 'Order'
The full stacktrace is here: https://gist.github.com/4020212
Thanks in advance and let me know if you would need more info!
EDIT: Question answered by randomhuman, code edited and usable as shown!
You do not need to explicitly pass a reference to self on this line:
val = self.__old_save(self, *args, **kwargs)
It is a method being called on an object reference. Passing it explicitly in this way is causing it to be seen as one of the other parameters of the save method, one which is expected to be a string or a number.

Categories