Count of ALL linked objects in django M2M object - python

A model is linked to several others using ManyToMany in the following manner:
class Tag(model.models):
attributes...
class A(model.models):
f = models.ManyToManyField(Tag)
class B(model.models):
f = models.ManyToManyField(Tag)
class C(model.models):
f = models.ManyToManyField(Tag)
And the objective is to get a count of ALL linked objects to Tag.
So something like count(A) + count(B) + count(C)
Is there a better way to do it than this:
count = 0
count += tag.a_set.count()
count += tag.b_set.count()
count += tag.c_set.count()
Also, what are the options for doing this programmatically when the linked models are unknown? I.e. We do not know the model name, so doing model_set is not possible.
A possible way:
# get count from all fields with '_set' at the end
linked_models = [
model_set
for model_set in dir(Tag)
if model_set.endswith('_set')
]
count = sum([
getattr(tag, model).count
for model in linked_models
])
Concerns:
performance on servers: this might take too much time to generate for each tag
there is a better way to do this: something hidden away in _meta perhaps that gets all linked objects together? I did not find anything (I'm sloppy!)

Related

Retrieve the Relationship object in Neomodel

I am using Neomodel and Python for my project. I have a number of nodes defined and am storing relevant information on the relationships between them. However I can't seem to find a mechanism for retrieving the relationship object itself to be able to use the attributes - I can only filter by the relationship attribute to return the Nodes.
class MyRelationship(StructuredRel):
source = StringProperty()
class Person(StructuredNode):
uid=UniqueIdProperty()
first_name = StringProperty()
last_name = StringProperty()
people = RelationshipTo('Person', "PERSON_RELATIONSHIP", model = MyRelationship)
I have a number of relationships of the same type [PERSON_RELATIONSHIP] between the same two nodes, but they differ by attribute. I want to be able to iterate through them and print out the to node and the attribute.
Given an Object person of type Person
for p in person.people:
gives me the Person objects
person.people.relationship(p).source always gives me the value for the first relationship only
A Traversal also seems to give me the Person objects as well
The only way it seems to get a Relationship object is on .connect.
Any clues? Thanks.
I just stumbled over the same problem and managed to solve it like below. But i am not sute if it is the most performant solution.
If you already have a Person node object in variable person:
for p in person.people:
r = person.people.relationship(p)
Or iterating over all Person nodes:
for person in Person.nodes.all():
for p in person.people:
r = person.people.relationship(p)
I've checked the neomodel source code and there doesn't seem to be a way to achieve what you want in a more efficient way than what Roman said.
but you could always use cypher queries.
from neomodel import db
from models import Person, MyRelationship
john = Person.nodes.get(name='John')
results, cols = db.cypher_query(f"""MATCH (node)-[rel]-(neighbor)
WHERE id(node)={john.id}
RETURN node, rel, neighbor""")
rels = {}
neighbors = []
for row in results:
neighbor = Person.inflate(row[cols.index('neighbor')])
neighbors.append(neighbor)
rel = MyRelationship.inflate(row[cols.index('rel')])
rels[neighbor.id] = rel
Then, now that you've stored all neighbors and the relationships between them, you can loop through them like so:
for neighbor, rel in rels:
print(f"john has a friendship with {neighbor} which has the source {rel.source}")
Hope this helps!
Ethan

Django filter by the number of rows matching a certain condition in a ManyToMany

I need to filter for objects where the number of elements in a ManyToMany relationship matches a condition. Here's some simplified models:
Place(models.Model):
name = models.CharField(max_length=100)
Person(models.Model):
type = models.CharField(max_length=1)
place = models.ManyToManyField(Place, related_name="people")
I tried to do this:
c = Count(Q(people__type='V'))
p = Places.objects.annotate(v_people=c)
But this just makes the .v_people attribute count the number of People.
Since python-2.0, you can use the filter=... parameter of the Count(..) function [Django-doc] for this:
Place.objects.annotate(
v_people=Count('people', filter=Q(people__type='V'))
)
So this will assign to v_people the number of people with type='V' for that specific Place object.
An alternative is to .filter(..) the relation first:
Place.objects.filter(
Q(people__type='V') | Q(people__isnull=True)
).annotate(
v_people=Count('people')
)
Here we thus filter the relation such that we allow people that either have type='V', or with no people at all (since it is possible that the Place has no people. We then count the related model.
This generates a query like:
SELECT `place`.*, COUNT(`person_place`.`person_id`) AS `v_people`
FROM `place`
LEFT OUTER JOIN `person_place` ON `place`.`id` = `person_place`.`place_id`
LEFT OUTER JOIN `person` ON `person_place`.`person_id` = `person`.`id`
WHERE `person`.`type` = V OR `person_place`.`person_id` IS NULL

Iterating through an unlimited number of child objects in a Django queryset

I have a Django model that defines a list of categories. Each category can be a child of another category, and it is possible that depth of this list of categories could go on quite a bit.
models.py
class Category(models.Model):
code = UUIDField(unique=True, primary_key=True)
name = models.CharField(max_length=100, null=True, blank=True, unique=True)
parent_cat = models.ForeignKey('self', null=True, blank=True)
Consider for example, a Category for women's clothing. It may have something like the following:
Women's > Clothing > Dresses > Summer
That all works fine, but separately I want to build a function that can build a single list containing an extrapolated view of every category in the tree. To do that, I have written the following:
queryset = Category.objects.filter(parent_cat=kwargs['code'])
all_children = []
all_children.append(queryset.values())
for c in queryset:
children = Category.objects.filter(parent_cat=c.code)
if children:
all_children.append(children.values())
all_children.append(children)
I realise this code is probably quite sloppy, I'm still learning!
As you probable realise, this will only give me two levels of child objects.
My question is: What is the most efficient way that I can write this code so that it will find every possible category until it reaches the last object with no more children?
Thanks!!
you could try using recursion something along the lines of this:
Replace list with the apprpriate data type.
def parseQueryset(queryset)
for x in queryset:
if isinstancex(x, list):
parseQueryset(x)
else:
#do stuff
You are looking for https://en.wikipedia.org/wiki/Tree_traversal - and you can use django-mptt package: https://django-mptt.github.io/django-mptt/overview.html#what-is-modified-preorder-tree-traversal

Create an instance that represents the average of multiple instances

I have a Review Model like the one defined below (I removed a bunch of the fields in REVIEW_FIELDS). I want to find the average of a subset of the attributes and populate a ModelForm with the computed information.
REVIEW_FIELDS = ['noise']
class Review(models.Model):
notes = models.TextField(null=True, blank=True)
CHOICES = ((1, u'Quiet'), (2, u'Kinda Quiet'), (3, u'Loud'))
noise = models.models.IntegerField('Noise Level', blank-True, null=True, choices=CHOICES)
class ReviewForm(ModelForm):
class Meta:
model = Review
fields = REVIEW_FIELDS
I can easily add more fields to the model, and then add them to the REVIEW_FIELDS list. Then I can easily iterate over them in javascript, etc. In my view, I want to compute the average of a bunch of the integer fields and populate a ReviewForm with the attribute values. How can I do something like this?
stats = {}
for attr in REVIEW_FIELDS:
val = reviews.aggregate(Avg(attr)).values()[0]
if val:
stats[attr] = int(round(val,0))
r.attr = stats[attr]
r = Review()
f = ReviewForm(r)
How can I create a ReviewForm with the average values without hard-coding the attribute values? I'd like to be able to add the field, add the value to the list, and have it automatically added to the set of statistics computed.
If I'm doing something fundamentally wrong, please let me know. I'm relatively new to django, so I might be re-inventing functionality that already exists.
After looking at the docs, I pass a dictionary to the ReviewForm when instantiating it:
f = ReviewForm(stats)
It seems to work pretty well! If anyone has any suggestions on a better way to do this, I'm all ears!

A puzzle concerning Q objects and Foreign Keys

I've got a model like this:
class Thing(models.Model):
property1 = models.IntegerField()
property2 = models.IntegerField()
property3 = models.IntegerField()
class Subthing(models.Model):
subproperty = models.IntegerField()
thing = modelsForeignkey(Thing)
main = models.BooleanField()
I've got a function that is passed a list of filters where each filter is of the form {'type':something, 'value':x}. This function needs to return a set of results ANDing all the filters together:
final_q = Q()
for filter in filters:
q = None
if filter['type'] =='thing-property1':
q = Q(property1=filter['value'])
elif filter['type'] =='thing-property2':
q = Q(property2=filter['value'])
elif filter['type'] =='thing-property2':
q = Q(property3=filter['value'])
if q:
final_q = final_q & q
return Thing.objects.filter(final_q).distinct()
Each Subthing has a Boolean property 'main'. Every Thing has 1 and only 1 Subthing where main==True.
I now need to add filter that returns all the Things which have a Subthing where main==True and subproperty==filter['value']
Can I do this as part of the Q object I'm constructing? If not how else? The queryset I get before my new filter can be quite large so I would like a method that doesn't involve looping over the results.
It's a bit easier to understand if you explicitly give your Subthings a "related_name" in their relationship to the Thing
class Subthing(models.Model):
...
thing = models.ForeignKey(Thing, related_name='subthings')
...
Now, you use Django join syntax to build your Q object:
Q(subthings__main=True) & Q(subthings__subproperty=filter['value'])
The reverse relationship has the default name 'subthing_set', but I find that it's easier to follow if you give it a better name like 'subthings'.
Using (instead of final_q=Q() in the beginning)
final_q=Q(subthing_set__main=True)
sub_vals = map(lambda v: v['value'], filters)
if sub_vals:
final_q = final_q & Q(subthing_set__subproperty__in=sub_vals)
should get you what you want, you can also adjust your loop to build the sub_vals list and apply it after the loop.
subthing_set is and automatically added related field added to the Thing to access related Subthings.
you can assign another related name, e.g.
thing=models.ForeignKey(Thing,related_name='subthings')

Categories