Django Tastypie: Imlementing Many To Many “through” relationships

安稳与你 提交于 2019-12-09 13:15:34

问题


I have searched about this issue a lot and gone through a bunch of related questions on Stack Overflow, but there doesn't seem to be a definitive answer about how to implement many-to-many relationships "through" an intermediate model (or maybe I missed it).

I have a model named Sample which has a many-to-many relationship with Region. There is an intermediate model which connects the two, named SampleRegion. I am currently not saving any extra information on the intermediate model, but I might in the future.

Here are my models:

class Sample(models.Model):
    sample_id = models.BigIntegerField(primary_key=True)
    description = models.TextField(blank=True)
    objects = models.GeoManager()
    regions = ManyToManyField(Region, through='SampleRegion')
    class Meta:
        db_table = u'samples'
    def save(self, **kwargs):
        # Assign a sample ID only for create requests
        if self.sample_id is None:
            try: id = Sample.objects.latest('sample_id').sample_id + 1
            except Sample.DoesNotExist: id = 1
            self.sample_id = id
        super(Sample, self).save

class Region(models.Model):
    name = models.CharField(max_length=100, unique=True)
    def __unicode__(self):
        return self.name
    class Meta:
        db_table = u'regions'

class SampleRegion(models.Model):
    sample = models.ForeignKey('Sample')
    region = models.ForeignKey(Region)
    class Meta:
        unique_together = (('sample', 'region'),)
        db_table = u'sample_regions'

And here is one approach I took to write the resources. It's not correct, and I am not able to figure out the right way of doing it:

class SampleResource(ModelResource):
    regions = fields.ToManyField("tastyapi.resources.RegionResource",
                                  "regions")
    class Meta:
        queryset = models.Sample.objects.all()
        allowed_methods = ['get', 'post', 'put', 'delete']
        authentication = ApiKeyAuthentication()
        authorization = ObjectAuthorization('tastyapi', 'sample')
        excludes = ['user', 'collector']
        filtering = {
                'version': ALL,
                'sesar_number': ALL
                }
        validation = VersionValidation(queryset, 'sample_id')

    def hydrate_regions(self, bundle): 
        # code to create a new SampleRegion object by getting a list of 
        # regions from bundle.data['regions']

class RegionResource(ModelResource):
    class Meta:
        queryset = models.Region.objects.all()
        allowed_methods = ['get']
        resource_name = "region"
        filtering = {
                'region': ALL,
                }

This is how I making a POST request:

post_data = {
    'regions': ["/tastyapi/v1/region/2/"],
    'description': 'Created by a test case',
}

client.post('/tastyapi/v1/sample/', data = post_data,
            authentication = credentials, format = 'json')

This request doesn't work because bundle.data['regions'] is None by the time it reaches hydrate_regions.

Does anybody have any advice on how I should go about implementing this scenario?


回答1:


I figured this out a couple of days ago. Here's what I found...

Django takes care of creating M2M relationships for you if you are not explicitly creating an intermediate table. However, if you are explicitly using an intermediate table, then you are responsible for creating a record in the intermediate table. To get this working in Tastypie, I had to override the save_m2m method to explicitly create a record in the intermediate table linking my the sample I have just created and an existing region.

This is how the relevant part of my resources.py looks like now:

class SampleResource(ModelResource):
    regions = fields.ToManyField("tastyapi.resources.RegionResource",
                                 "regions")

    class Meta:
        queryset = models.Sample.objects.all()
        allowed_methods = ['get', 'post', 'put', 'delete']
        authentication = ApiKeyAuthentication()
        authorization = ObjectAuthorization('tastyapi', 'sample')
        excludes = ['user', 'collector']
        filtering = {
                'regions': ALL_WITH_RELATIONS,
                }
        validation = VersionValidation(queryset, 'sample_id')

    def save_m2m(self, bundle):
        for field_name, field_object in self.fields.items():
            if not getattr(field_object, 'is_m2m', False):
                continue

            if not field_object.attribute:
                continue

            for field in bundle.data[field_name]:
                kwargs = {'sample': models.Sample.objects.get(pk=bundle.obj.sample_id),
                          'region': field.obj}

                try: SampleRegion.objects.get_or_create(**kwargs)
                except IntegrityError: continue

class RegionResource(BaseResource):
    class Meta:
        queryset = models.Region.objects.all()
        authentication = ApiKeyAuthentication()
        allowed_methods = ['get']
        resource_name = "region"
        filtering = { 'region': ALL }


来源:https://stackoverflow.com/questions/22601223/django-tastypie-imlementing-many-to-many-through-relationships

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!