Check if objects is foreign key of queryset result - python

I have a set of objects stored in a variable called subs. The column subscribed_to is a foreign object.
Is there any way to do check if a related_object is in this list in a simpler way:
def check_subscription_status(user, related_object):
subs = get_user_subscriptions(user) # returns filter queryset
subscribed = False
for s in subs:
if s.subscribed_to == related_object: #related object is the potential match
subscribed = True
break
return subscribed

return (related_object in [s.subscribed_to for s in subs])
ETA: A better way:
return subs.filter(subscribed_to=related_object).count() > 0

Related

Django find item in queryset and get next

I'm trying to take an object, look up a queryset, find the item in that queryset, and find the next one.
#property
def next_object_url(self):
contacts = Model.objects.filter(owner=self.owner).order_by('-date')
place_in_query = list(contacts.values_list('id', flat=True)).index(self.id)
next_contact = contacts[place_in_query + 1]
When I add this to the model and run it, here's what I get for each variable for one instance.
CURRENT = Current Object
NEXT = Next Object
contacts.count = 1114
self.id = 3533 #This is CURRENT.id
place_in_query = 36
contacts[place_in_query] = NEXT
next_contact = CURRENT
What am i missing / what dumb mistake am i making?
In your function, contacts is a QuerySet. The actual objets are not fetched in the line:
contacts = Model.objects.filter(owner=self.owner).order_by('-date')
because you don’t use a function like list(), you don’t iterate the QuerySet yet... It is evaluated later. This is probably the reason of your problem.
Since you need to search an ID in the list of contacts and the find the next object in that list, I think there is no way but fetch all the contact and use a classic Python loop to find yours objects.
#property
def next_object_url(self):
contacts = list(Model.objects.filter(owner=self.owner).order_by('-date').all())
for curr_contact, next_contact in zip(contacts[:-1], contacts[1:]):
if curr_contact.id == self.id:
return next_contact
else:
# not found
raise ContactNotFoundError(self.id)
Another solution would be to change your database model in order to add a notion of previous/next contact at database level…

Django queryset flipping

Is there any way to flip the relationship in a Django querset? Such as turning a Foo queryset into a Bar queryset if all Foo objects have a foreign key to Bar?
Take the following example:
#condition_n are previously defined statements that results in either True or False
if condition_1:
foo_qs = Foo.objects.filter(bar__something=...)
if condition_2:
foo_qs = foo_qs.filter(bar__another_thing=...)
if condition_3:
#you get the point
now this will result in some queryset foo_qs depending on the evaluation of the condition_n type statements. If I wanted to also get the bar queryset that would result from that, I could do the following:
#condition_n are previously defined statements that results in either True or False
if condition_1:
foo_qs = Foo.objects.filter(bar__something=...)
bar_qs = Bar.objects.filter(something=...)
if condition_2:
foo_qs = foo_qs.filter(bar__another_thing=...)
bar_qs = Bar.objects.filter(another_thing=...)
if condition_3:
#you get the point
but ideally I would like to take any queryset, and give it the name of the foreign key ("bar" in this case), and have it flip to the equivalent queryset. Is there any way about doing this?
Such as turning a Foo queryset into a Bar queryset if all Foo objects have a foreign key to Bar?
Why not? Here's one approach, if you always write the base query as the one local to the object in question.
bar_query = {
'something': 'something',
}
def prefix_fk(query, prefix):
prefixed_query = {}
for key, value in query.items():
prefixed_query[prefix+key] = value
return prefixed_query
Foo.objects.filter(**prefix_fk(bar_query, 'bar__'))
Bar.objects.filter(**bar_query)
if condition_2:
bar_query['another_query'] = 'foobar'
Foo.objects.filter(**prefix_fk(bar_query, 'bar__'))
Bar.objects.filter(**bar_query)

Compare old and updated fields in Django model

I want to compare old and updated field in model. I have did this issue for one field but i want do this for all fields:
class MyUser(User)
def save(self, **kwargs):
if self.pk is not None:
orig = MyUser.objects.get(pk=self.pk)
orig_field_names = orig._meta.get_all_field_names()
field_names = self._meta.get_all_field_names()
# I want do this in loop
if orig.first_name != self.first_name:
print 'first_name changed'
UpdateLog.objects.create(
user = orig,
filed_name = self.first_name,
update_time = datetime.now()
)
super(MyUser, self).save(**kwargs)
Thanks in advance
Here's my go-to function for comparing fields. Gets a little hairy when dealing with foreign keys, but it's not too bad overall:
def get_changes_between_objects(object1, object2, excludes=[]):
"""
Finds the changes between the common fields on two objects
:param object1: The first object
:param object2: The second object
:param excludes: A list of field names to exclude
"""
changes = {}
# For every field in the model
for field in object1._meta.fields:
# Don't process excluded fields or automatically updating fields
if not field.name in excludes and not isinstance(field, fields.AutoField):
# If the field isn't a related field (i.e. a foreign key)..
if not isinstance(field, fields.related.RelatedField):
old_val = field.value_from_object(object1)
new_val = field.value_from_object(object2)
# If the old value doesn't equal the new value, and they're
# not both equivalent to null (i.e. None and "")
if old_val != new_val and not(not old_val and not new_val):
changes[field.verbose_name] = (old_val, new_val)
# If the field is a related field..
elif isinstance(field, fields.related.RelatedField):
if field.value_from_object(object1) != field.value_from_object(object2):
old_pk = field.value_from_object(object1)
try:
old_val = field.related.parent_model.objects.get(pk=old_pk)
except field.related.parent_model.DoesNotExist:
old_val = None
new_pk = field.value_from_object(object2)
try:
new_val = field.related.parent_model.objects.get(pk=new_pk)
except field.related.parent_model.DoesNotExist:
new_val = None
changes[field.verbose_name] = (old_val, new_val)
return changes
Usage:
>>> item = Item.objects.get(pk=1)
>>> item_old = Item.objects.get(pk=1)
>>> print item.my_attribute
'foo'
>>> item.my_attribute = 'bar'
>>> get_changes_between_objects(item, item_old)
{'My Attribute': ('bar', 'foo')}
You want a signal. For quick reference, here's the introductory paragraph or so from that link:
Django includes a “signal dispatcher” which helps decoupled
applications get notified when actions occur elsewhere in the
framework. In a nutshell, signals allow certain senders to notify a
set of receivers that some action has taken place. They’re especially
useful when many pieces of code may be interested in the same events.
Django provides a set of built-in signals that let user code get
notified by Django itself of certain actions.
Read the docs before vote -1. Catch the signal is the better way to do this thing

Django: Saving old QuerySet for future comparison

I'm new with django and I'm trying to make a unit test where I want to compare a QuerySet before and after a batch editing function call.
def test_batchEditing_9(self):
reset() #reset database for test
query = Game.objects.all()
query_old = Game.objects.all()
dict_value = {'game_code' : '001'}
Utility.batchEditing(Game, query, dict_value)
query_new = Game.objects.all()
self.assertTrue(compareQuerySet(query_old, query_new))
My problem is that query_old will be updated after batchEditing is called. Therefor, both querysets will be the same.
It seem that QuerySet is bound to the current state of the database.
Is this normal?
Is there a way to unbind a QuerySet from the database?
I have tried queryset.values, list(queryset) but it still updates the value.
I'm actually thinking about iterating on the queryset and creating a list of dictionaries by myself, but I want to know if there is an easier way.
Here is batchEditing (didn't paste input validity check)
def batchEditing(model, query, values):
for item in query:
if isinstance(item, model):
for field, val in values.iteritems():
if val is not None:
setattr(item, field, val)
item.save()
Here is compareQuerySet
def compareQuerySet(object1, object2):
list_val1 = object1.values_list()
list_val2 = object2.values_list()
for i in range(len(list_val1)):
if list_val1[i] != list_val2[i]:
return False
return True
A Queryset is essentially just generating SQL and only on evaluating it, the database is hit. As far as I remember, that happens on iterating over the Queryset. For instance,
gamescache = list(Game.objects.all())
or
for g in Game.objects.all():
...
hit the database.
Following code should work:
def test_batchEditing_9(self):
reset() #reset database for test
query = Game.objects.all()
query_old = set(query)
dict_value = {'game_code' : '001'}
Utility.batchEditing(Game, query, dict_value)
query_new = set(query)
self.assertEqual(query_old, query_new)
This is because Game.objects.all() is not hitting database, but just creates object that stores query parameters.
BTW. If you will use order_by in query and order is important you can use list rather than set.

Django model ON DELETE CASCADE policy, emulate ON DELETE RESTRICT instead, general solution

I'd like to delete an instance of a model, but only if it doesn't have any other instance of another class with a foreign key pointing to it. From Django documentation:
When Django deletes an object, it emulates the behavior of the SQL constraint ON DELETE CASCADE -- in other words, any objects which had foreign keys pointing at the object to be deleted will be deleted along with it.
In a given example:
class TestA(models.Model)
name = models.CharField()
class TestB(models.Model)
name = models.CharField()
TestAs = models.ManyToManyField(TestA)
# More classes with a ManyToMany relationship with TestA
# ........
I'd like something like:
tA = TestA(name="testA1")
tB = TestB(name="testB1")
tB.testAs.add(tA)
t = TestA.objects.get(name="testA1")
if is_not_foreignkey(t):
t.delete()
else:
print "Error, some instance is using this"
Should print the error. I know I can check for specific instances the foreignkey sets, like in this case check t.TestB_set(), but I am looking for a more general solution for any given model.
I finally solved it using this Nullable ForeignKeys and deleting a referenced model instance, the solution looks like:
# Check foreign key references
instances_to_be_deleted = CollectedObjects()
object._collect_sub_objects(instances_to_be_deleted)
# Count objects to delete
count_instances_to_delete = 0
for k in instances_to_be_deleted.unordered_keys():
count_instances_to_delete += len(instances_to_be_deleted[k])
if count_instances_to_delete == 1:
object.delete()
else:
pass
Check the related objects length
t=TestA.objects.get(name="textA1")
if not t.testB_set.all().count():#related members
t.delete()
CollectedObjects() was removed in Django 1.3 -- here's a current method:
from compiler.ast import flatten
from django.db import DEFAULT_DB_ALIAS
from django.contrib.admin.util import NestedObjects
def delete_obj_if_no_references(obj):
collector = NestedObjects(using=DEFAULT_DB_ALIAS)
collector.collect([obj])
objs = flatten(collector.nested())
if len(objs) == 1 and objs[0] is obj:
obj.delete()
return True
return False

Categories