简体   繁体   中英

get_or_create django model with ManyToMany field

Suppose I have three django models:

class Section(models.Model):
    name = models.CharField()

class Size(models.Model):
    section = models.ForeignKey(Section)
    size = models.IntegerField()

class Obj(models.Model):
    name = models.CharField()
    sizes = models.ManyToManyField(Size)

I would like to import a large amount of Obj data where many of the sizes fields will be identical. However, since Obj has a ManyToMany field, I can't just test for existence like I normally would. I would like to be able to do something like this:

try:
    x = Obj(name='foo')
    x.sizes.add(sizemodel1) # these can be looked up with get_or_create
    ...
    x.sizes.add(sizemodelN) # these can be looked up with get_or_create
    # Now test whether x already exists, so I don't add a duplicate
    try:
        Obj.objects.get(x)
    except Obj.DoesNotExist:
        x.save()

However, I'm not aware of a way to get an object this way, you have to just pass in keyword parameters, which don't work for ManyToManyFields.

Is there any good way I can do this? The only idea I've had is to build up a set of Q objects to pass to get:

myq = myq & Q(sizes__id=sizemodelN.id)

But I am not sure this will even work...

Use a through model and then .get() against that.

http://docs.djangoproject.com/en/dev/topics/db/models/#extra-fields-on-many-to-many-relationships

Once you have a through model, you can .get() or .filter() or .exists() to determine the existence of an object that you might otherwise want to create. Note that .get() is really intended for columns where unique is enforced by the DB - you might have better performance with .exists() for your purposes.

If this is too radical or inconvenient a solution, you can also just grab the ManyRelatedManager and iterate through to determine if the object exists:

object_sizes = obj.sizes.all()
exists = object_sizes.filter(id__in = some_bunch_of_size_object_ids_you_are_curious_about).exists()
if not exists: 
    (your creation code here)

Your example doesn't make much sense because you can't add m2m relationships before an x is saved, but it illustrated what you are trying to do pretty well. You have a list of Size objects created via get_or_create() , and want to create an Obj if no duplicate obj-size relationship exists?

Unfortunately, this is not possible very easily. Chaining Q(id=F) & Q(id=O) & Q(id=O) doesn't work for m2m.

You could certainly use Obj.objects.filter(size__in=Sizes) but that means you'd get a match for an Obj with 1 size in a huge list of sizes.

Check out this post for an __in exact question , answered by Malcolm, so I trust it quite a bit.

I wrote some python for fun that could take care of this.
This is a one time import right?

def has_exact_m2m_match(match_list):
    """
    Get exact Obj m2m match 
    """
    if isinstance(match_list, QuerySet):
        match_list = [x.id for x in match_list]

    results = {}
    match = set(match_list)
    for obj, size in \
        Obj.sizes.through.objects.filter(size__in=match).values_list('obj', 'size'):
        # note: we are accessing the auto generated through model for the sizes m2m
        try:
            results[obj].append(size)
        except KeyError:
            results[obj] = [size]

    return bool(filter(lambda x: set(x) == match, results.values()))
    # filter any specific objects that have the exact same size IDs
    # if there is a match, it means an Obj exists with exactly 
    # the sizes you provided to the function, no more.


sizes = [size1, size2, size3, sizeN...]
if has_exact_m2m_match(sizes):
    x = Obj.objects.create(name=foo) # saves so you can use x.sizes.add
    x.sizes.add(sizes)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM