• Django Tastypie: 贴士,技巧和故障排除


    https://monicalent.com/blog/2014/10/31/django-tastypie-reverse-relationships-filtering/ -- 2014.10.31

    Tastypie是Django最受欢迎的REST API框架之一,如果你已经熟悉了Django Model,那么使用它会相当轻松。

    不过,它会很难debug,会生成一些诡秘的错误消息。

    下面是一些我在使用框架工作时需要解决的问题,贴士和解决问题的方法,以及一些反馈意见。

    为Resource加入字段

    为Resource加入字段看起来很简单,也确实很简单 -- 但是,有很多方式可以做到,所以你需要决定使用哪种方式才是适合你的。

    1.为字段实现专门的dehydrate函数

    from tastypie import fields
    from tastypie.resources import ModelResource
    
    from app.models import MyModel
    
    
    class MyModelResource(ModelResource):
        FOO = fiels.CharField()
        
        class Meta:
            queryset = MyModel.objects.all()
            
        def dehydrate_FOO(self):
            return bundle.obj.data.FOO.upper()
    

    在这里,我们在函数名中的下划线后面指明了对象引用(也就是函数dehydrate_FOO会操作FOO字段,在函数中通过bundle.obj来访问.如果你通过某种方式进行了更新,Tastypie会为你自动更新bundle.data['FOO'].

    2.实现(resource级别的)dehydrate方法

    from tastypie import fields
    from tastypie.resources import ModelResource
    
    from app.models import MyModel
    
    
    class MyModelResource(ModelResource):
        class Meta:
            queryset = MyModel.objects.all()
            
        def dehydrate(self, bundle):
            bundle.data['new_Foo'] = 'This came from nowhere!'
            return bundle
    

    如果你要基于或者不基于其它现有的字段来增加新的字段,使用这种方法就说的通。

    3.额外的方法

    另外还有一些不同的方式可以手动为Tastypie Resource增加字段。如果你有需要,可以查看下面的一些文章资源:

    排除故障

    'Bundle'对象不可以使用字典赋值语法.

    如果你想要为bundle而不是bundle.data进行字典赋值,会出错。请确保你在操作字段的时候,作用的是bundle.data这个字典.

    bundle['new_field'] = 'This will not work.'
    bundle.data['new_field'] = 'This works!'
    

    通过外键,外键的反向关系来映射一个对象的属性

    这个章节的标题不太好理解,让我给你一些使用场景:

    • 我有一个Grammer话题对象列表
    • 这些话题的内容,以很多不同的语言来编写
    • 每个Content都对Grammer话题有一个ForeignKey关系
    • 在查看Grammer的list view的时候,我想同时看到可用内容的不同语言标题

    我的初始JSON:

    {
        "meta": {
            "limit": 20,
            "next": "/api/v1/grammar/?offset=20&limit=20&format=json",
            "offset": 0,
            "previous": null,
            "total_count": 1
        },
        "objects": [
            {
                "id": 18,
                "resource_uri": "/api/v1/grammar/18/",
                "name": "First Declension Nouns - Feminine (α-stem)",
            }
        ]
    }
    

    我的目标JSON:

    {
        meta: {
            limit: 20,
            next: "/api/v1/grammar/?offset=20&limit=20&format=json",
            offset: 0,
            previous: null,
            total_count: 1
        },
        objects: [
            {
                id: 18,
                resource_uri: "/api/v1/grammar/18/",
                name: "First Declension Nouns - Feminine (α-stem)",
                titles: {
                    de: "Die a-Deklination",
                    en: "First Declension Nouns",
                    it: "Sostantivi femminili"
                }
            }
        ]
    }
    

    你可以看到,目标是把关联的content标题组建为字典的形式,使用它们语言的short_code作为字典的键。我们需要首选获取content,通过grammer来过滤,最后将它们映射为字典。

    下面是相关的Django Models:

    import textwrap
    
    from django.db import models
    
    
    class Language(models.Model):
        name = models.CharField("Language name(english)",
                                max_length=200,
                                help_text=('e.g. German)')
        short_code = models.CharField('shortcode',
                                      max_length=5,
                                      
        def __unicode__(self):
            return unicode(self.name) or u""
            
            
    class Grammer(models.Model):
        name = models.CharField("title of grammer section",
                                max_length=200,
                                help_text=textwrap.dedent("""
                                    Short, descriptive title of the grammer 
                                    concept.
                                """))
            
    
    class Content(models.Model):
        title = models.CharField('title',
                                 max_length=200,
                                 help_text=textwrap.dedent("""
                                    Short, descriptive title of the grammer 
                                    concept.
                                """))
        grammer_ref = models.ForeignKey(Grammer,
                                        verbose_name='grammer topic',
                                        null=True,
                                        blank=True,
                                        help_text=textwrap.dedent("""
                                            The morphology directly
                                            described by this content.
                                        """))
        source_lang = models.ForeignKey(Language,
                                        related_name='content_written_in',
                                        help_text=textwrap.dedent("""
                                            Language the content is written
                                            in.
                                        """))
        target_lang = models.ForeignKey(Language,
                                        related_name='content_written_about',
                                        help_text="Language the content teaches.")
        content = models.TextField("Learning Content",
                                   help_text=textwrap.dedent("""
                                        Write this in Markdown.
                                   """))
        
        def __unicode__(self):
            return unicode(self.title) or u""
    

    api/grammer.py - GrammerResource使用dehydrate函数来为resource对象增加新的字段,另外加入了一个helper函数用来帮助reduce掉content对象列表。

    from tastypie import fields
    from tastypie.resources import ModelResource
    
    from app.models import Grammer, Content
    from api.content import ContentResource
    
    
    class GrammerResource(ModelResource):
        # 在这里,我们使用反向关系来获取和这个grammer关联的content对象
        content = fields.ToManyField(ContentResource, 'content_set',
                                     related_name='content',
                                     blank=True,
                                     null=True,
                                     use_in='detail',
                                     full=True)
        
        class Meta:
            queryset = Grammer.objects.all()
            allowed_methods = ['get']
            
        def build_title(self, memo, content):
            lang = content.resource_lang.short_code
            memo[lang] = content.title
            return momo
            
        def dehydrate(self, bundle):
            bundle.data['titles'] = reduce(self.build_title, 
                                           Content.objects.filter(grammer_ref=bundle.obj), {})
            return bundle
    

    如果你已经习惯map/reduce,那么这个代码是能够自解释的。

    额外的资源

    通过关系来过滤

    有一件事,Tastypie看起来没有良好的支持,就是通过值来过滤model的关系。

    请考虑下面的使用场景:

    • 你有一个Taskmodel
    • 你想要使用另一个model(TaskSequence)来排序这个tasks
    • 你将一个Task和一个TaskSequence以及其它的元数据关联起来(通过through关系,一个叫做TastContext的model,它包含task顺序的信息)

    如果你只告诉Tastypie要`TaskSequence,你只会得到下面的数据:

    {
       id: 60,
       name: "The Aorist Tense",
       query: "pos=verb&tense;=aor",
       ref: "s542,s546",
       resource_uri: "/api/v1/grammar/60/",
       task_sequence: {
          id: 2,
          name: "Verbs for Beginners",
          resource_uri: "",
          tasks: [
              {
                  endpoint: "word",
                  hint_msg: "Try again.",
                  id: 4,
                  name: "identify_morph:person",
                  success_msg: "Good job!"
              },
              {
                  endpoint: "word",
                  hint_msg: "Try again.",
                  id: 5,
                  name: "identify_morph:number"
                  success_msg: "Good job!"
              }
        ]
    }
    

    不过,我们需要关心的through表,来决定task的顺序。我们希望的JSON是下面这样:

    {
       id: 60,
       name: "The Aorist Tense",
       query: "pos=verb&tense;=aor",
       ref: "s542,s546",
       resource_uri: "/api/v1/grammar/60/",
       task_sequence: {
          id: 2,
          name: "Verbs for Beginners",
          resource_uri: "",
          tasks: [
              {
                  id: 4,
                  max_attempts: 10,
                  order: 0,
                  resource_uri: "",
                  target_accuracy: 0.5,
                  task: {
                      endpoint: "word",
                      hint_msg: "Try again.",
                      id: 4,
                      name: "identify_morph:person",
                      success_msg: "Good job!"
                  }
              },
              {
                  id: 5,
                  max_attempts: 5,
                  order: 1,
                  target_accuracy: 0.8,
                  task: {
                      endpoint: "word",
                      hint_msg: "Try again.",
                      id: 5,
                      name: "identify_morph:number",
                      success_msg: "Good job!"
                  }
              }
        ]
    }
    

    相关Resources

    请注意看下面的三个Tastypie Resource。有趣的代码出现在`TaskSequenceResource。我们在这里通过through表来过滤了tasks.

    """
    api/task.py
    """
    from tastypie.resources import ModelResource
    
    from app.models import Task
    
    
    class TaskResource(ModelResource):
        
        class Meta:
            queryset = Task.objects.all()
            allowed_methods = ['get']
    
    """
    api/task_context.py
    """
    from tastypie import fields
    from tastypie.resources import ModelResource
    
    from app.models import TaskContext
    
    
    class TaskContextResource(ModelResource):
        task = fields.ToOneField('api.task.TaskResource',
                                 'task',
                                 full=True,
                                 null=True,
                                 blank=True)
        
        class Meta:
            queryset = TaskContext.objects.all()
            allowed_methods = ['get']
    
    """
    api/task_consequence.py
    """
    from tastypie import fields
    from tastypie.resources import ModelResource
    
    from app.models import TaskSequence
    
    
    class TaskSequenceResource(ModelResource):
        tasks = fields.ManyToManyFields('api.task.TaskContextResource',
                                        attribute=lambda bundle:
                                        bundle.obj.tasks.through.objects.filter(
                                            task_sequence=bundle.obj) or bundle.obj.tasks,
                                        full=True)
        
        class Meta:
            queryset = TaskSequence.objects.all()
            allowed_methods = ['get']
    

    故障排查

    对象是没有through属性的.

    请注意,lambda bundle: bundle.obj.through.objects是错的,因为中间缺少了tasks。through只有在queryset中有。

    自引用Resources

    有时候,Model需要引用自身。例如,有一个Person model,这个model可能会有很多人际关系(关系的类型也是Person)。

    难点出来了,在每个Person Model都有一个关系列表的时候。很可能会出现下面的错误:

    Maximum recursion depth exceeded

    1.让Model的关系不对称(并且不要设置full=True)

    在我们的例子中,这意味着PersonA可以关联PersonB,但是反过来不行。

    这让Tastypie可以很容易的处理:

    # app/models.py
    
    from django.db import models
    
    
    class Person(models.Model):
        relatives = models.ManyToManyField('self',
                        related_name='relates_to',
                        symmetrical=False,
                        null=True,
                        blank=True)
    
    # api/person.py
    
    from tastypie import fields
    from tastypie.resources import ModelResource
    
    from myapp.models import Person
    
    
    class PersonResource(ModelResource):
        relatives = fields.ToManyField('self', 'relatives')
        
        class Meta:
            queryset = Person.objects.all()
    

    2.使用use_in选项

    from tastypie import fields
    from tastypie.resources import ModelResource
    
    from myapp.models import Person
    
    
    class PersonResource(ModelResource):
        relatives = fields.ToManyField('self', 'relatives', use_in='list')
        
        class Meta:
            queryset = Person.objects.all()
    

    使用这种方式,relatives字段不会在detail view中显示。

    3.创建'shallow'版本的resource

    如果你需要在你的list view中使用full=True。最简单避免无限递归的方式是,创建两个resource:

    # api/person.py
    from tastypie import fields
    from tastypie.resources import ModelResource
    
    from myapp.models import Person
    from api.relative import RelativeResource
    
    
    class PersonResource(ModelResource):
        relatives = fields.ManyToManyField(RelativeResource,
                                           'relatives',
                                           null=True,
                                           blank=True,
                                           full=True)
                                           
        class Meta:
            queryset = Person.objects.all()
            allow_methods = ['get'] 
    
    # api/relative.py
    from tastypie import fields
    from tastypie.resource import ModelResource
    
    from myapp.models import Person
    
    
    class RelativeResource(ModelResource):
        class Meta:
            queryset = Person.objects.all()
            allow_methods = ['get']
    

    请注意,只有PersonResource设定了full=True。因为RelativeResource没有设定m2m字段,所以就不会进入无尽循环。

    故障排查

    Options对象没有api_name属性

    请注意你要指向resource,而不是model。

    额外资源

  • 相关阅读:
    HttpUtils
    其实就是这么回事
    Spring 、 CXF 整合 swagger 【试炼】
    Jetty 学习记录
    WebSphere 学习记录
    Apache 学习记录
    WebLogic 学习记录
    Hessian 学习记录
    IntelliJ IDEA学习记录
    IntelliJ IDEA学习记录
  • 原文地址:https://www.cnblogs.com/thomaszdxsn/p/Django-Tastypie-tie-shi-ji-qiao-he-gu-zhang-pai-ch.html
Copyright © 2020-2023  润新知