Django and multiple joins - python

I'm trying to do a quite long join, but it seems like django can't handle the last layer. Am I getting it wrong or is there any way around this?
Models.py
class A(models.Model):
object_id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=50)
class B(models.Model):
class_A_object = models.ForeignKey(A, on_delete=models.PROTECT)
value = models.CharField(max_length=50)
class C(models.Model):
class_B_object = models.ForeignKey(B, on_delete=models.PROTECT)
class D(models.Model):
value= models.IntegerField(primary_key=True)
class_C_object = models.ForeignKey(C, on_delete=models.PROTECT)
I'm then trying to select the value in class D when the related class A object name = ABC.
D.objects.filter(class_C_object__class_B_object__class_A_object__name='ABC')
This fails, to begin with pycharm refuses the autocomplete it and if I run it i get a name not defined error.
However, if i drop one layer it works.
D.objects.filter(class_C_object__class_B_object__value='ABC')
I haven't found any documentation mentioning a maximum number of joins, but it feels like there is a limitation here.
Does anyone know if that's the case, and if so, what the best way would be to work around this?
The database is external to me and can not be modified. The only working decent workaround i have at the moment is to use the cursor directly and write the sql by hand. However, this is not ideal for a lot of reasons.
Any help would be much appreciated.
Kind Regards,
Peter

I have solved this, maybe not the neatest solution, but by getting a queryset of A matching name ABC and then using that queryset for a __in filter on D get the result I want, and it's just doing one query.
It works, and is efficient, but the code is not very clean. If anyone has a suggestion of another way to do this i would be very happy to hear.
Thanks,
Peter

Related

How to prefetch a count and use it to annotate a queryset in Django ORM

I’m trying to perform an advanced SQL query but I can’t figure out how to do this.
My goal is to implement a read/unread system for a Forum, so i have the followings models (incomplete for :
succinctness)
class ReadActivity(models.Model):
content_object = GenericForeignKey('content_type', 'object_id')
reader = models.ForeignKey(User, null=False, blank=False)
read_until = models.DateTimeField(blank=False, null=False, default=timezone.now)
class Meta:
unique_together = (("content_type", "object_id", "reader"),)
class Readable(models.Model):
read_activities = GenericRelation(ReadActivity)
class Meta:
abstract = True
class Forum(models.Model):
pass
class Topic(Readable):
pass
I think this is quite self explanatory : Topics can be marked as read. A user has zero or one ReadActivity per topic. (In fact Forum is also readable but this is out of scope for this question)
So : I want to be able to fetch a forum list, and for each forum determine if it is read or unread (for a given user)
My approach is :
For each forums, if any topic do not have any ReadActivity or a ReadActivity where read_until < last_message, mark the forum as unread.
Speaking SQL, I would do that this in the following way :
SELECT COUNT("forums_topic"."id")
FROM "forums_topic"
LEFT JOIN "reads_readactivity"
ON ("forums_topic"."id" = "reads_readactivity"."object_id" AND "reads_readactivity"."content_type_id" = '11')
WHERE (("reads_readactivity"."id" IS NULL
OR ("reads_readactivity"."reader_id" = '1' AND "reads_readactivity"."read_until" < "forums_topic”."last_message_date"))
AND "forums_topic"."forum_id" IN ('1', '2'))
GROUP BY "forums_topic"."forum_id"
But I can’t find a way to do this using Django ORM (Without many many queries). I tried to use Prefetch :
topics_prefetch = Topic.objects.filter(Q(read_activities=None) | (Q(read_activities__reader=self.request.user) & Q(read_activities__read_until__gt=F('created_at'))))
queryset = queryset.prefetch_related(Prefetch('read_activities', queryset=ReadActivity.objects.filter(reader=self.request.user)))
But this solution is not good enough, because there is no way I can select COUNT(), or limit to 1 result, and group the count by forum.
I tried to combine this solution with annotate :
queryset = queryset.annotate(is_unread=Count('topics__read_activities’))
But this just don’t work, it counts ALL the topics
Then I tried to use annotate with case/when :
queryset = queryset.annotate(is_unread=Case(
When(topics=None, then=False),
When(topics__read_activities=None, then=True),
When(topics__read_activities__reader=self.request.user, topics__read_activities__read_until__lte=F('topics__created_at'), then=True),
default=False,
output_field=BooleanField()
))
But it does not give the right results. Moreover I’m not sure this is the best solution. I’m not sure how CASE/WHEN SQL are implemented, but if it iterates internally on every topics, this may be too expensive
Now I’m considering writing RawSQL, however I don’t know how to add a manager method which would let be do something like : Forums.objects.filter().with_unread(), because I don’t know how to access the filtered forums to build a query like
"forums_topic"."forum_id" IN ('1', '2'))
from the manager itself.
Maybe I’m missing something, maybe you know how to do that. Any help would be appreciated, thank you !

Large ManyToMany relations on django admin form

So, I have the following models:
class Band(models.Model):
name = models.CharField(max_length=50)
class Contract(models.Model):
band = models.ForeignKey(Band)
when = models.DateTimeField(auto_now_add=True)
salary = models.IntegerField()
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
bands = models.ManyToManyField(Band, through=Contract)
class Album(models.Model):
artist = models.ForeignKey(Musician)
name = models.CharField(max_length=100)
release_date = models.DateField()
num_stars = models.IntegerField()
So, I wanted to expose that on the admin page. So far, so good.
Note that our musicians here keep jumping in and out from bands. Some say one of them even had been on over 2 millions bands in his life-time. I don't know, maybe the bands are Whitesnake, Metallica or something.
How should we do that on the Django Admin Page?
I tried using raw_id_fields and apart the fact I didn't like the effect, it didn't work so well. It took a lot of time to load and it didn't let me add more ids. Weird.
I've used admin.StackedInline with no luck cause it will try to load every contract in a which, well, it's gonna take only 2 thousand years.
When Musician had a direct relation to Band it worked just fine with this library. But now that the relation isn't an straight one. Looks like autocomplete doesn't support it(it was getting slow anyway).
So, with all of this, I ask you lord SO members. What's the best way to do this? Is it autocomplete? Someone must have had to come across this issue!
Thanks in advance.
To avoid loading every bands in your admin page use autocomplete_fields Django doc.
Just use it like that in your admin.py.
autocomplete_fields = ('bands',)
Then no bands will be pulled from DB to front, but you will be able to select it through a Select2 search field and it will be printed as "tags".
I found this solution and hope it will help somebody in the same situation:
I have many to many relations between the Product and Characteristic model.
So, in the admin.py I am setting a form for a Product like the following where catch/get all the Characteristics and make the "prefecth_related" for Characteristic, as well the "select_related" could be done there:
class ProductAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['characteristics'].queryset = Characteristic.objects.prefetch_related('category').all()

Django filter many-to-many from an id

I have a problem, I'm coding with python/django.
I have this kind of models.py :
class Skill(models.Model):
name = models.CharField()
skill=models.ForeignKey(Skill)
class Job(models.Model):
name = models.CharField()
skill= models.ManyToManyField(Skill)
And my problem is: I want to print for one Job, all the skills which are attached.
Example: I have the Developer Job, with the ID=4 (in my db), and I want to print all the
skill which are attached with the Developer Job.
Tks !!
If you have the Job item already, in a variable called my_job:
my_job.skill.all()
If you just have the ID:
Skill.objects.filter(job__id=4)
I believe you will also find useful a possibility to get to get all Jobs, which are referenced bu some Skill. It's easy like that:
ninja_skill = Skill.objects.get(name='ninja')
jobs_with_ninja_skill = ninja_skill.job_set.all()

Best practice for accessing distantly related Django models

Let say we have a long chain of Django models, where each references the one above through a ForeignKey field:
class One(models.Model):
# fields
class Two(models.Model):
one = models.ForeignKey(One)
...
class Ten(models.Model):
nine = models.ForeignKey(Nine)
Good! Now image, if you will, having an instance of the Ten model and wanting to grab the related One instance. This can result in long lines of attribute chaining like this:
ten_instance.nine.eight.seven.six.five.four.three.two.one
I'm wondering what the standard approach would be to this niggling issue. Do we leave it as is, being inherently descriptive and readable. Or do we aim to shorten such a line to make things more simple:
ten_instance.one
 But What's The Best Practice Here? Or is there a more simple solution?
Use Properties
My current approach would be to add a property to the Ten model, abstracting away that attribute chaining:
class Ten(models.Model):
nine = models.ForeignKey(Nine)
#property
def one(self):
return self.nine.eight.seven.six.five.four.three.two.one
I can see a downside to this tactic however, and that's the added mysticism involved. Does the Ten instance actually have a relation to the One model or not? I wouldn't be able to tell without inspecting the model myself.
You probably want to use django-mptt for sophisticated hierarchal models although it can be a bit ott. If you want a simple hierarchy then add a ForeignKey to self:
class Number(models.Model):
parent = models.ForeignKey('self', blank=True, null=True,
related_name='child')
then the query would be something like this based on a unique field, say slug:
Number.objects.get(parent__slug='one')

quick question: clear an attribute of a model in django

i think this is a pretty easy question for you.
I want to clear an attribute of a django-model.
If i have something like this:
class Book(models.Model):
name = models.TextField()
pages = models.IntegerField()
img = models.ImageField()
In an abstract function i want to clear an attribute, but at that time i don't know what type the field has. So examples would be name="", pages=0 or img=None.. Is there a way to do it in a generic way? I search something like SET_TO_EMPTY(book,"pages")
Do you know a function like that?
many thanks in advance!
I don't think there is a clean way of doing it. However, assuming you've set a default value (which you don't have) you can do it like this:
book.img = book._meta.get_field('img').default
Do note that your current model won't allow a None value. To allow those you have to set null=True and blank=True. For pages you need a default=0.

Categories