Django queryset flipping - python

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)

Related

Django if elif else statement

I'm trying to print the result according to the user's age selection in the form, but my if,elif and else statements are not working.
class Quiz(models.Model):
age_choices = (('10-12', '10-12'),
('13-16', '13-16'),
('17-20', '17-20'),
('21-23','21-23'),
)
age = models.CharField(max_length = 100, choices = age_choices)
views.py
def create_order(request):
form = QuizForm(request.POST or None)
if request.method == 'POST':
quiz = Quiz.objects
if quiz.age=='10-12':
print("10-12")
elif quiz.age=='13-16':
print("13-16")
elif quiz.age=='17-20':
print("17-20")
elif quiz.age=='21-23':
print("21-23")
else:
return None
context = {'form':form}
return render(request, "manualupload.html", context)
quiz = Quiz.objects will return a django.db.models.manager.Manager object and this can be further used to fetch the objects from database belonging to that particular model. The appropriate query set will be quiz = Quiz.objects.all() Then you will get the list of all objects in that belong to Quiz model. Once you get list of all objects, you can get the specific object either by indexing or by filtering using a specific query that you need to look into and then for that particular object you can get the age property.
Refer to official django documentation about creating queries for more information.
As #Abhijeetk431 mentioned, your issue lies in quiz = Quiz.objects.
If you use type(quiz), you will find that it outputs django.db.models.manager.Manager. This is not what you want, as age is a property of the Quiz class, not the Manager class.
For starters, refer to this.
This will return you a Queryset list, something akin to an Excel table. age is akin to the column in the table. To get age, what you want is the row (the actual Quiz object) in said table, which you can achieve using get or using the square brackets [].
Thus, your code should look something like this:
Model.objects.all()[0]
That would return the correct object(only the first row) and allow you to get the column value.
However, further clarification will be needed though, to know exactly what your problem is aside from 'it doesn't work'. How did you know your code is not working; what did the debugger tell you?

What is the most idiomatic way to express this query with SQAlchemy?

Essentially I have a SQLAlchemy query that looks like this:
foos = Foo.query.filter(Foo.expiration < cutoff)
valid_foos = []
for foo in foos:
last_bar = foo.bars.order_by('created_at desc').first()
if last_bar.state != 'fatal':
valid_foos.append(foo)
The goal being to select all the foos for which the first related bar's state is not "fatal". It seems like subqueries might help here. However, I'm struggling to grasp how I can express last_bar = foo.bars.order_by('created_at desc').first() in this way.
I think the easiest and most versatile way of doing it is using Hybrid Attributes extension. When you extend you model as per below:
class Foo(Base):
__tablename__ = "foo"
# ...
#hybrid_property
def last_bar_state(self):
_last_bar = self.bars.order_by(Bar.created_at.desc()).first()
return _last_bar.state
#last_bar_state.expression
def _last_bar_state_expression(cls):
q = (select([Bar.state.label("state")])
.where(Bar.foo_id == cls.foo_id)
.order_by(Bar.created_at.desc())
.limit(1)
).label("last_bar_state_sub")
return q
you will be able to use last_bar_state both in-memory, as well as in the query:
foos = session.query(Foo).filter(Foo.expiration < cutoff)
foos = foos.filter(Foo.last_bar_state != 'fatal').all()
Use a subquery to find the latest Bar for each Foo. Then use that to join and filter Bar when querying Foo.
sub = db.session.query(
Foo.id,
db.func.max(Bar.created_at).label('latest')
).join(Foo.bars).group_by(Foo.id).subquery()
foos = db.session.query(Foo).join(Bar, db.and_(
Bar.foo_id == sub.c.id,
Bar.created_at == sub.c.latest
)).filter(Bar.status != 'fatal').all()
This will not select Foo if it has no Bar, you can use an outer join instead for that.

Check if objects is foreign key of queryset result

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

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