django - compare two objects [using fields dynamically] - django

I need compare two objects, to determine if a field has changed or not
class Country(models.Model): # country code 'MX' -> Mexico
code = models.CharField(max_length=2)
name = models.CharField(max_length=15)
class Client(models.Model): # id=1, name=pedro, country.code=MX, rfc=12345
name = models.CharField(max_length=100)
country = models.ForeignKey(Country)
rfc = models.CharField(max_length=13)
> obj_db = Client.object.get(id=1)
> country = Country.objects.get(code='MX')
obj_no_db = Client(**{'id':1, 'name':'pedro', 'country': country, 'rfc':12345})
> obj_db == obj_no_db # True
> obj_no_db = Client(**{'id':1, 'name':'pedro', 'country': country, 'rfc':1})
> obj_db == obj_no_db # True # but isn't True because the rfc has change, how can compare field by field
> obj_db.rfc == obj_no_db.rfc # False I expected this result
I need to build a function to do it generic, the problem i don't found information about it, i think i can use the ._meta options, but i'm not sure.
I developed this function but i can't discover the way to compare field by field.
def get_insert_update(obj, key, obj_list, fields=None, exclude_fields=None):
"""
:param obj: The object for compare
:param key: a the key for compare to determine if we need to update or insert
:param obj_list: list objects to compare
:return: to_insert, _update
"""
db = {}
to_insert = []
to_update = []
if key == 'pk': # the field pk doesn't exists so we change to id, because its the same
key = 'id'
exclude_fields = exclude_fields or []
fields = fields or []
if 'pk' in fields:
fields[fields.index('pk')] = 'id' # we change the field pk, because it doesn't exists
if 'pk' in exclude_fields:
exclude_fields[exclude_fields.index('pk')] = 'id' # we change the field pk, because it doesn't exists
meta = obj._meta # we define meta object
if fields is None:
fields = meta.get_all_field_names()
fields = [f for f in meta.fields if f.attname in fields]
# dumping db into memory
for _obj in obj.objects.all():
if isinstance(key, list): # first check if is a list to create a custom key
_key = _get_key(_obj, key)
else:
_key = _obj.__dict__[key]
# if exclude fields exists
if exclude_fields:
d = {f.attname: _obj.__dict__[f.attname] for f in fields if f.attname not in exclude_fields}
db[_key] = obj(**d)
else: # we save the full object
db[_key] = _obj
# read local objects to determine if the record will be insert or update
for _obj in obj_list:
if isinstance(key, list): # first check if is a list to create a custom key
_key = _get_key(_obj, key)
else:
_key = _obj.__dict__[key]
if _key in db: # if the key is in db so we check if it equal
# if _obj.pk == 6: # debug
# print(_obj.__dict__, db[_key].__dict__, _obj.__dict__ == db[_key].__dict__)
if _obj != db[_key]: # HERE i need the determine if the fields are equal or not.
to_update.append(_obj) # if the object has changed, we update it
else:
pass # if the object is equal, we didn't do it anything
else:
to_insert.append(_obj) # because we didn't found into the database, we create it
return to_insert, to_update
def _get_key(obj, lst):
"""
create a string key using multiples keys
Example: obj.id -> 1, obj.name -> 'foo'
lst['id', 'name']
:param lst: list of keys
:return: 1_foo
"""
k = []
for t in lst:
k.append(str(obj.__dict__[t]))
return "_".split(k)

Django's Model class defines the __eq__ method to compare based on the value of the pk attribute, which is why your models compare equal.
One simple way to do this would be to override that method on your own model to compare the value of the __dict__, which contains all the instance's values.
There's a slight gotcha with this, in that __dict__ also contains a hidden _state object that will be compared by ID, so you'd need to filter that out of the comparison.
def __eq__(self, other):
values = [(k,v) for k,v in self.__dict__.items() if k != '_state']
other_values = [(k,v) for k,v in other.__dict__.items() if k != '_state']
return values == other_values

Related

Django: Filter objects by a range of two integers

I have a search form with id, price_from, price_to and drive_from, driven_too, but since price and driven are the same logic I am only focusing in one of these, since price_from and price_to are not in the model, it gives me an error because this parameter does not exist, therefore it can not compare anything.
How can i search in the car objects for an object which price is within this range (price_from - price_to), should i modify my model?
# model
class Car(models.Model):
id = models.CharField(max_length=255)
number = models.CharField(max_length=255)
price = models.IntegerField()
# view
def search(request):
if 'search_filter' in request.GET:
search_params = request.GET.dict()
search_params.pop("search_filter")
price_from = search_params['price_from']
price_to = search_params['price_to']
q_list = [Q(("{}__icontains".format(param), search_params[param])) for param in search_params
if search_params[param] is not None]
my_query = Car.objects.filter(reduce(operator.and_, q_list))
cars = [{
'id': x.id,
'driven': x.driven,
'price': x.price,
} for x in my_query
]
return JsonResponse({'data': cars})
context = {'cars': Car.objects.all().order_by('price')}
return render(request, 'cars/car_index.html', context)
You can construct a Q(..) object with:
Q(price__gte=price_from, price__lte=price_to)
or you can exclude the bounds with:
Q(price__gt=price_from, price__lt=price_to)
although it is possible that price_from or price_to is None, and thus construct a Q object with kwargs:
if 'search_filter' in request.GET:
search_params = request.GET.dict()
search_params.pop('search_filter')
price_from = search_params.pop('price_from', None)
price_to = search_params.pop('price_to', None)
q_list = [
Q(('{}__icontains'.format(k), v))
for k, v in search_params.items()
if v is not None
]
price = { 'price__gte': price_from, 'price__lte': price_to }
q_list.append(Q(**{k: int(v) for k, v in price.items() if v is not None}))
# ...

insert a data into mysql database if the field valus is change

I have developed a code for update a database,but right now I want to code if the value is change the database insert the data not update the database.Here is my code:-
def update_Sprint(self, ps):
id = ps.projectid
data=requests.get('https://example.com/api/v1/xyz/'+str(ps.projectid)+'/Iterations?include=[Id,Name,StartDate,EndDate,UserStories-Effort-Sum,UserStories-EffortCompleted-Sum]&token='+settings.AUTH_TOKEN+'&format=json')
data=json.loads(data.text)
data=data['Items']
if len(data)>0:
for data_ in data:
sprint_id=str(data_['Id']
sps = Day.objects.filter(sprintid=sprint_id, project__projectid=ps.projectid)
print ' creating new sprint '+str(sprint_id)
sp = Day(sprintid=sprint_id, project=ps)
sp = update_model(sp, sprint_name = Name,sprint_end_date = EndDate, sprint_start_date = StartDate, usesteff_sum = Effor_Sum,usesteffc_sum = Effort_Com,)
#self.update_story(sp)
#self.update_Bug(sp)
def update_model(obj, **params):
"""
:param obj: generic model object
:param params: fields, value dict of the obj's model
:return: updated object
"""
is_updating = False
for key in params:
try:
field = getattr(obj, key)
value = params[key]
if field==value:
print 'same value'
elif ((not value) and (not field)): print 'No value for '+key
else:
print 'updating value'
is_updating = True
setattr(obj, key, value)
# obj.field = value
except AttributeError as ex:
print ex
if is_updating:
obj.save()
return obj
How can I insert the data into database if the field and value are not same.I don't want to update the table.

replacing field in queryset before return

I have the following view -
class DeployFilterView(generics.ListAPIView):
serializer_class = DefinitionSerializer
def get_queryset(self):
jobname = self.request.GET.get('jobname')
if jobname.count("\\") == 1:
jobname = jobname.replace("\\", "")
queryset = Jobmst.objects.db_manager('Admiral').filter(jobmst_name=jobname).exclude(jobmst_prntname__isnull=False, jobmst_dirty='X')
else:
parent, job = jobname.rsplit('\\', 1)
queryset = Jobmst.objects.db_manager('Admiral').filter(jobmst_prntname=parent, jobmst_name=job).exclude(jobmst_dirty='X')
return queryset
In that view there's a value field called "jobmst_runbook" which has a character that doesn't translate with the DRF XML renderer. What I'd like to be able to do is scan the queryset for a particular character - SOH or \u0001
If it finds this character I want to remove it before doing the return queryset
I solved this by doing the logic in my serializer. The serializer is now looking for the object that's causing the failure and stripping out the character.
class DefinitionSerializer(serializers.ModelSerializer):
runbook_url = serializers.SerializerMethodField('get_url')
# dependencies = serializers.RelatedField(many=True)
jobdep = serializers.HyperlinkedRelatedField(
source='jobdep_set', # this is the model class name (and add set, this is how you call the reverse relation of bar)
view_name='jobdep-detail' # the name of the URL, required
)
# triggers = serializers.RelatedField(many=True)
trgmst = serializers.HyperlinkedRelatedField(
source='trgmst_set', # this is the model class name (and add set, this is how you call the reverse relation of bar)
view_name='trgmst-detail' # the name of the URL, required
)
class Meta:
model = Jobmst
resource_name = 'jobmst'
depth = 2
fields = ('jobmst_id', 'jobmst_type', 'jobmst_prntid', 'jobmst_active', 'evntmst_id',
'jobmst_evntoffset', 'jobmst_name', 'jobmst_mode', 'jobmst_owner', 'jobmst_desc',
'jobmst_crttm', 'jobdtl_id', 'jobmst_lstchgtm', 'runbook_url', 'jobcls_id', 'jobmst_prntname',
'jobmst_alias', 'jobmst_dirty', 'job_dependencies', 'job_events')
def get_url(self, obj):
if obj.jobmst_runbook == None:
pass
else:
return force_text(obj.jobmst_runbook[:-5])

Combine values from MultiWidget into one field for POST

I'm using the phone number MultiWidget provided by derek73. Question is: it puts three separate values in the POST - how should I recombine them into one value?
class USPhoneNumberMultiWidget(forms.MultiWidget):
"""
A Widget that splits US Phone number input into three <input type='text'> boxes.
"""
def __init__(self,attrs=None):
widgets = (
forms.TextInput(attrs={'size':'3','maxlength':'3', 'class':'phone'}),
forms.TextInput(attrs={'size':'3','maxlength':'3', 'class':'phone'}),
forms.TextInput(attrs={'size':'4','maxlength':'4', 'class':'phone'}),
)
super(USPhoneNumberMultiWidget, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
return value.split('-')
return (None,None,None)
def value_from_datadict(self, data, files, name):
value = [u'',u'',u'']
# look for keys like name_1, get the index from the end
# and make a new list for the string replacement values
for d in filter(lambda x: x.startswith(name), data):
index = int(d[len(name)+1:])
value[index] = data[d]
if value[0] == value[1] == value[2] == u'':
return None
return u'%s-%s-%s' % tuple(value)
class UserForm(forms.ModelForm):
email = forms.EmailField(max_length=30,
widget=forms.TextInput(attrs={"class":"text"}))
first_name = forms.CharField(max_length=max_length_for_att(User, 'first_name'),
widget=forms.TextInput(attrs={"class":"text"}))
last_name = forms.CharField(max_length=max_length_for_att(User, 'last_name'),
widget=forms.TextInput(attrs={"class":"text"}))
phone = USPhoneNumberField(label="Phone",
widget=USPhoneNumberMultiWidget())
class Meta:
model = User
fields = ('email', 'first_name', 'last_name', 'phone', )
Look at the USPhoneNumberField's clean() method.
Make sure you are accessing the data from form.cleaned_data and not request.POST.

Control Query set in Django (filter,object Q)?

Base On URL
querydict = {customer_type:val1,tag:[], city:[],last_contact:valdate}
show/?customer_type=All&tag=2,3&city=3&last_contact=29/12/2009
I am going to filter by made the method:
def get_filter_result(customer_type, tag_selected, city_selected, last_contact_filled):
if customer_type=has value:
I filter by this
#queryset = Customer.objects.filter(Q(type__name=customer_type))
if tag = has value :
I filter by this
#queryset = Customer.objects.filter(Q(type__name=customer_type)
Q(type__name=tag))
if city = has value:
I filter by this
#queryset = Customer.objects.filter(Q(type__name=customer_type)
Q(type__name=tag),
Q(type__name=city))
if last_contact = has value:
I filter by this
#queryset = Customer.objects.filter(Q(type__name=customer_type)
Q(type__name=tag),
Q(type__name=city),
Q(type__name=last_contact))
Anybody Help give an idea to implement my method more simple and flexible than this?
if value of them are missing or equal None(No value is passed)
so if... else.... condition will control alots of time and code will be larger..
for example :
show/?customer_type=All&tag=&city=&last_contact=
show/?customer_type=All&tag=2,3&city=3&last_contact=29/12/2009
show/?customer_type=&tag=2,3&city=3&last_contact=
show/?customer_type=All&tag=2,3&city=&last_contact=29/12/2009
thanks
def get_filter_result(customer_type=None, tag_selected=None, city_selected=None, last_contact_filled=None):
qdict = {}
if customer_type is not None:
qdict['type__name'] = customer_type
if tag is not None:
<repeat as appropriate>
queryset = Customer.objects.filter(**qdict)
If you want to AND all your queries (like in your example), you can create dictionary of parameters, and then call filter method with this dictionary as an argument:
def get_filter_result(**kwargs):
params = {}
#delete items with empty strings
for key in kwargs:
if kwargs[key]:
params[key] = kwargs[key]
queryset = Customer.objects.filter(**params)