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 Configs 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.

Вернуться на верх