Merge two queries in one single query in django - django-queryset

I have a model as follow:
class ModelA(models.Model):
id = models.CharField()
class ModelB(models.Model):
name = models.CharField()
base = models.Boolean(default=False)
modela = models.ForeignKey(ModelA)
In ModelB we have records as below:
id name base modela
------------------------------------------------
1 solution_base True X2ZQ
2 solution_x False X2ZQ
3 solution_base True ALSB
4 solution_z False ALSB
5 solution_base True 5YET
6 solution_c False 5YET
7 solution_base True PIAT
... ... ... ...
As you can see each record has a base copy of itself that can be distinguished by unique modela foreignkey. All I need, is that by a given normal solution id(example solution_x) I need to query its base equivalent base object (where modela ids are the same). Here what I did so far:
modela_id = ModelB.objects.filter(id=modelb_pk).select_related('modela_id').values_list('modela_id', flat=True)
modelb_solution_base_id = ModelB.objects.filter(modela_id=modela_id[0]).filter(base=True).select_related('modela_id').values_list('id', flat=True)
I guess there should be a solution to merge these two using prefetch_related(Prefetch()) but I have no idea how to use that. Any help will be highly appreciated.

I think you're making this a bit more complicated than necessary -- the Django ORM handles much of this for you, thanks to that foreign-key relationship. Given an ID for ModelB, the ID for the other ModelB with the same ModelA but where base=True is just:
ModelB.objects.get(id=modelb_pk).modela.modelb_set.get(base=True).id
Why does this work?
Because ModelB has a many-to-one relationship with ModelA, we can call .modela on an instance of ModelB to get the corresponding ModelA.
Conversely, given an instance of ModelA, calling .modelb_set returns all ModelB records associated with ModelA.
We can then call .get/.filter on modelb_set just like we would with ModelB.objects.

Related

Create Query 'has one' relationship in reverse direction GORM

I am currently trying to create a new record using GORM and these two models has a one to one relationship with each other. Model1 has 'has one' relationship with Model2. I was thinking if it is possible to create query Model2 instead of Model1 in this case. Here is an example from the docs:
So in the docs context, is it possible to create query from the CreditCard struct as I want to persist the 'has one' relationship.
I managed to solve it! You can simply just include the foreign key in the struct model when creating it. For example:
CreditCard{
Number: "41111111111111"
UserID: <include the id here> // make sure the credit card gorm model has UserID foreign key specified
}
db.Create(&CreditCard)

Does eloquent pivot tables work for multi-word table names?

I have two multi-word models, let's call them FunkyModel and AnotherModel.
Will creating a pivot table named another_model_funky_model work?
The docs and examples I've come across all use single word model names like this: model A - User, model B - Address, and pivot table will then be address_user.
If you dive into the source code of the BelongsToMany relation function, you'll find that if you haven't provided a $table, the code will execute the function joiningTable. This uses the current model and the passed related class, snake cases the names and then puts them in alphabetical order of each other.
Simply said, no matter if you have a single word or a couple, the result will always be the 2 classes snaked, in alphabetical order. Note that the alphabetical order is applied by the default php sort.
Examples:
Department + Occupation > department_occupation
AwesomeModel + LessInterestingModel > awesome_model_less_interesting_model
Role + UserPermission > role_user_permission
You can even try and see what the auto-generated name is by simply calling the following:
(new Model)->joiningTable(OtherModel::class, (new OtherModel));
Yes it would work, you can also name it whatever you want, you just need to declare the table name in the relation (same goes for the foreign keys)
class FunkyModel
{
public function anotherModels()
{
return $this->belongsToMany(AnotherModel::class, 'pivot_table_name', 'funky_model_id', 'another_model_id');
}

ManyToMany with and whereIn

I have a ManyToMany relationship between AdInterest and AdInterestGroup models, with a belongsToMany() method in each model so I can use dynamic properties:
AdInterest->groups
AdInterestGroup->interests
I can find all the "interests" in a single group like this:
$interests = AdInterestGroup::find(1)->interests->pluck('foo');
What I need is a merged, deduplicated array of the related 'foo' field from multiple groups.
I imagine I can deduplicate with ->unique(), but first, as you'd expect, this:
AdInterestGroup::whereIn('id',[1,2])->interests->get();
throws:
Property [interests] does not exist on the Eloquent builder instance.
The advice seems to be to use eager loading via with():
AdInterestGroup::with('interests')->whereIn('id',[1,2])->get();
Firstly, as you'd expect that's giving me an array of two values though (one for each ID).
Also, if I try and pluck('foo') again, it's looking in the wrong database table: from the AdInterestGroup table, rather than the relationship (AdInterest).
Is there a nice, neat Collection method / pipeline I can use to combine the data and get access to the relationship fields?
Use pluck() and flatten():
$groups = AdInterestGroup::with('interests')->whereIn('id', [1, 2])->get();
$interests = $groups->pluck('interests')->flatten();
$foos = $interests->pluck('foo')->unique();

How to add multiple conditions on Laravel elequent relationship

I am new to Laravel and working on 5.4 version. I have a model "A" and model "B". Model A has hasMany relationship with B. Upto here things are ok. But now, I want to add more conditions on this relationship.
By default, Laravel works only foreign key relation. I mean it matches data on the basis of only 1 condition. Here, I want more condition.
Below are the tables of both Model:
table: A
id name Year
1 ABC 2016
2 DEF 2017
table: B
id A_id name Year
1 1 tst 2016
2 2 fdf 2017
3 1 kjg 2017
By default, If I want to see records of A_id 1 from table B, then Laravel will fetch all records for A_id 1. But now, suppose, if I want to get records for 2016 then How I can I do this using Laravel relationship method?
Below are the Elequent Model for both tables:
class A extends Model{
public function b(){
return this->hasMany('B');
}
}
class B extends Model{
public function a(){
return $this->belongsTo('A');
}
}
Code to fetch records of id 1:
$data = A::with('b')->find(1)->toArray();
The above request is giving me all data from table B for id 1 but I also want to put corresponding year condition also.
Is anyone know, Ho can I do this? Please share your solutions.
Thanks
You have to constrain your eager loads, using an array as the parameter for with(), using the relationship name as the key, and a closure as the value:
$data = A::with([
'b' => function ($query) {
$query->where('year', '=', '2016');
},
])->find(1)->toArray();
The closure automatically gets injected an instance of the Query Builder, which allows you to filter your eager loaded model.
The corresponding section in the official documentation: https://laravel.com/docs/5.4/eloquent-relationships#constraining-eager-loads

How can I add multiple instances to a Django reverse foreign key set in a minimal number of hits to the database?

For example, I have two models:
class Person(models.Model):
name = models.CharField(max_length=100)
class Job(models.Model):
title = models.CharField(max_length=100)
person = models.ForeignKey(Person)
I have a list of job ids--
job_ids = [1, 2, ....]
that are pks of Job model instances
I know I can do--
for id in job_ids:
person.jobs.add(job_id)
but this will be many more queries than if I could do--
person.jobs.add(job_ids)
where it would unpack the list and use bulk_create. How do I do this? Thanks.
Have you tried
person.jobs.add(*job_ids)
In my case I used a filter query and had a list of objects (as opposed to IDs). I was getting an error similar to
TypeError: 'MyModel' instance expected, got [<MyModel: MyModel Object>]
...before I included the asterisk.
credit (another SO question)
If you didn't create your jobs yet, you can create them by adding bulk=False
jobs_list=[
Job(title='job_1'),
Job(title='job_2')
[
person.jobs.add(*jobs_list, bulk=False) # your related_name should be 'jobs', otherwhise use 'job_sets'.

Resources