Django custom `get_or_create` on model with M2M through model
As title says I'm trying to implement get_or_create
method which able to get or create instance with respect to through
model.
Models:
class Component(models.Model):
name = CharField()
class ConfigComponent(models.Model):
config = models.ForeignKey(Config)
component = models.ForeignKey(Component)
quantity = models.PositiveIntegerField()
class Config(models.Model):
components = models.ManyToManyField(Component, through="ConfigComponent")
So basically I want to check if Config
matching input data exists and use it if so. Otherwise - create a new one. But how to consider the quantity
of each Component
? It is not a big deal to find Config
with exact num of components but it may appears that components match but their quantity is different.
def get_or_create(
self,
components: list[tuple[Component, int]]
) -> tuple[Config, bool]:
component_names = [component[0] for component in components]
components_count = len(components)
configs_match = Config.objects.annotate(
total_components=Count('components'),
matching_components=Count(
'components',
filter=Q(components__in=component_names)
))\
.filter(
total_components=components_count,
matching_components=components_count
)
Use:
from django.db.models import Count, Q
configs_match = Config.objects.alias(
count=Count('components'),
nfilter=Count('components', filter=Q(components__in=my_components)),
).filter(
count=len(my_components),
nfilter=len(components),
)
This will look for an exact match, since the Config
s need to have the same number of components
as len(my_components)
, and this should also hold for all components
in my_components
.
Or to also account for integers:
from django.db.models import Count, Q
configs_match = Config.objects.alias(
count=Count('components'),
nfilter=Count(
'components',
filter=Q(
*[
Q(component=c, configcomponent__quantity=q)
for c, q in my_components
],
_connector=Q.OR
),
),
).filter(
count=len(my_components),
nfilter=len(my_components)),
)
with my_components
a list of 2-tuples with the first item the Component
, and the second item the corresponding quantity.