2

I have three models in my models.py as follows:

class Service(models.Model):
     name = models.CharField(max_length=50, unique=True)
     port = models.PositiveSmallIntegerField()
     protocol = models.CharField(max_length=50)

class ServiceGroup(models.Model):
     name = models.CharField(max_length=50, unique=True)
     services = models.ManyToManyField(Service, through=ServiceToServiceGroup)

class ServiceToServiceGroup(models.Model):
    service = models.ForeignKey(Service)
    service_group = models.ForeignKey(ServiceGroup)

My JSON payload to create a new service group is as follows:

    {
    "name": "test_service_group1",
    "services":["service_1", "service_2"],
    }

Since I have a M2M through table, my strategy to create a new ServiceGroup is to first pop out the services list, create the ServiceGroup with the name and then create the M2M realtionships.

My serializer to create a new ServiceGroup is as follows:

class ServiceGroupCreateUpdateSerializer(serializers.ModelSerializer):
    services = serializers.SlugRelatedField(queryset=Service.objects.all(), 
                                            slug_field='name', many=True)
    class Meta:
        model = ServiceGroup
        fields = ['id', 'name', 'services']

    def create(self, validated_data):
        # Pop the services list out
        services = validated_data.pop('services', None)
        # Create the ServiceGroup with the name 
        service_group = ServiceGroup.objects.create(name=validated_data['name'])
        #Create M2M associations
        for service in services:
            service_id = Service.objects.get(name=service)
            ServiceToServiceGroup.objects.create(service_id=service_id,
                                                 service_group_id= service_group.id)

My question is how do I now write the update method ? My JSON payload remains the same, the only difference being that I pass the instance id in the URL.The pseudo code is as follows:

  1. Pop the services list.
  2. Save the name to the instance id.
  3. Find the existing services linked to the ServiceGroup.
  4. For services common in the existing list and the JSON payload list, do nothing.
  5. For services in the existing list and not in the payload, delete M2M associations.
  6. For services not in the existing list and in the payload, create M2M associations.

This seems like so much work for an update method. Is there an easier way of doing this ?

UPDATE

In [145]: instance = ServiceGroup.objects.get(pk=1)                                                                                                                                                                                                                      

In [146]: instance.services.all()                                                                                                                                                                                                                                        
Out[146]: <QuerySet [<Service: test-create442>]>

In [147]: new_services_list = ['test-create398']                                                                                                                                                                                                                         

In [148]: service_objects = 
Service.objects.filter(name__in=new_services_list).all()                                                                                                                                                                                     

In [149]: service_objects                                                                                                                                                                                                                                                
Out[149]: <QuerySet [<Service: test-create398>]>

In [150]: instance.service_set = service_objects                                                                                                                                                                                                                         

In [151]: instance.save()                                                                                                                                                                                                                                                

In [152]: instance.services.all()                                                                                                                                                                                                                                        
Out[152]: <QuerySet [<Service: test-create442>]>

So, I tried the above and it did not work.

Amistad
  • 7,100
  • 13
  • 48
  • 75

1 Answers1

1

you can override the update method

def update(self, instance, validated_data):
    # Pop the services list out
    services = validated_data.pop('services', None)
    instance = super().update(instance, validated_data)
    service_objects = Service.objects.filter(name__in=services).all()
    ServiceToServiceGroup.objects.filter(service_group=instance).delete()
    service_group_obj = []
    for service in service_objects:
        service_group_obj(ServiceToServiceGroup(service=service, service_group=instance))
    ServiceToServiceGroup.objects.bulk_create(service_group_obj)
    return instance
aman kumar
  • 3,086
  • 1
  • 17
  • 24
  • This looks really nice...Can you elaborate on how instance.service_set works ? – Amistad Dec 10 '18 at 10:58
  • django provide reverse access for the relational fields, using related_name, if don't define the related_name default is modelname_set https://stackoverflow.com/questions/17328910/django-what-is-reverse-relationship – aman kumar Dec 10 '18 at 11:03
  • ..I tried this..this does not seem to work..The M2M relationships do not get updated.. – Amistad Dec 11 '18 at 06:51
  • no error..it just does not update the M2m table..also notice that there is a through table..does the update need to happen via that ? – Amistad Dec 11 '18 at 08:42
  • updated fields name will be services not service_set – aman kumar Dec 11 '18 at 08:59
  • that does not work..it gives an error..TypeError: Direct assignment to the forward side of a many-to-many set is prohibited. Use services.set() instead. and on using a set, it gives the error..AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use ServiceToServiceGroup's Manager instead. – Amistad Dec 11 '18 at 09:05
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/185048/discussion-between-amistad-and-aman-kumar). – Amistad Dec 11 '18 at 09:05