【问题标题】:Django forgets .using database on resolving foreign keyDjango 忘记使用数据库解析外键
【发布时间】:2016-12-15 10:46:26
【问题描述】:

我有一个由 manage.py inspectdb 创建的旧数据库模型,它访问设置中名为“edlserver”的数据库,该数据库是该项目使用的众多数据库之一。我无法更改数据库布局。

它有以下类(以及其他不相关的类):

一个用于记录条目。

class Logs(models.Model):
  time = models.DateTimeField()
  job = models.ForeignKey(Jobs, models.DO_NOTHING, db_column='id_job')
  msg = models.TextField()

  class Meta:
    managed = False
    db_table = 'logs'

另一个作业(作业字段引用)

class Jobs(models.Model):
  job_type = models.ForeignKey(JobTypes, models.DO_NOTHING, db_column='id_job_type')
  time_start = models.DateTimeField()
  time_end = models.DateTimeField(blank=True, null=True)
  pid = models.IntegerField(blank=True, null=True)
  title = models.CharField(max_length=255)

  class Meta:
    managed = False
    db_table = 'jobs'

还有一个用于 JobTypes。

class JobTypes(models.Model):
  name = models.CharField(max_length=255)
  max_processes = models.IntegerField()

  class Meta:
    managed = False
    db_table = 'job_types'

django-rest-framework 的视图如下所示

class EDLLogList(generics.ListAPIView):
  serializer_class = EDLLogsSerializer
  filter_backends = (filters.DjangoFilterBackend, )
  filter_class = EDLLogsFilter    

  def get_queryset(self):
    if not 'job_name' in self.request.GET:
      raise ParameterRequired('job_name')
    else:
      return Logs.objects.all().using('edlserver')

它使用过滤器:

class EDLLogsFilter(filters.FilterSet):
  time_start = django_filters.DateTimeFilter(name="time", lookup_type='gte')
  time_end   = django_filters.DateTimeFilter(name="time", lookup_type='lte')
  job_name   = django_filters.MethodFilter()      

  class Meta:
    model = Logs
    fields = ()

  def filter_job_name(self, queryset, job_name):
    try:
      q = queryset.filter(job__job_type__name=job_name)[:10000]
    except:
      raise InternalError()

    if len(q) < 1 and 
       len(JobTypes.objects.all().using('edlserver').filter(name=job_name)) < 1:
         raise InvalidParameter(job_name, 'job_name')
    else:
        return q

和序列化器:

class EDLLogsSerializer(serializers.HyperlinkedModelSerializer):
  time = serializers.DateTimeField()
  job_name = serializers.SerializerMethodField()
  message = serializers.SerializerMethodField()

  class Meta:
    model = Logs
    fields = ('job_name','time', 'message')

  def get_job_name(self, obj):
    return obj['id_job__id_job_type__name']

  def get_message(self, obj):
    return obj.msg

问题是我在序列化程序中的get_job_name() 中得到一个TypeError: 'Logs' object is not subscriptable,来自 psycopg2 模块 - 但是,该数据库是 MySQL 数据库。第一个查询在调试期间有一个 len > 0 的查询集这一事实表明该模型是可以的,并且 django 使用 MySQL 后端来获取数据。在解析外键时出现问题并且(我认为)使用默认数据库,即 PostGreSQL。

这是一个错误吗?

如果没有,我该怎么办?我在考虑一个路由器,它可以解析一个 Meta 字段。这将意味着对其他模型进行大量更改,所以我不想这样做。有什么想法吗?

编辑:数据库设置

'default': {
    'ENGINE': 'django.db.backends.postgresql_psycopg2',
    'NAME': 'pic',
    'USER' : 'pic-5437',
    'PASSWORD' : '',    
    'HOST' : 'host1.url.com',
    'PORT' : '5432'    
},
'...' : {
    ...
},
'edlserver': {
    'ENGINE': 'django.db.backends.mysql',
    'HOST': 'host2.url.com',
    'NAME': 'edl',
    'USER': 'edl_ro',
    'PASSWORD': '',
}

【问题讨论】:

  • 请用您的 settings.py 的数据库部分更新您的问题,并省略不必要的模型
  • 模型都是需要的,因为它们显示了外键关系。我在数据库中进行了编辑。

标签: python mysql django django-rest-framework serialization


【解决方案1】:

外键为模型添加一个属性,该属性本身就是模型实例。要遵循从 LogsJobType 的完整关系,只需使用属性查找:

def get_job_name(self, obj):
    return obj.job.job_type.name

这将是正常的用例。如果使用多个数据库,django在解析外键时使用了错误的数据库,可以手动完成:

 return JobTypes.objects.all().using('edlserver').filter(id=
   Jobs.objects.all().using('edlserver').filter(id=
   obj.job_id)[0].job_type_id)[0].name

另一种选择是在模型中引入 Meta 字段,如下所示:

import django.db.models.options as options
options.DEFAULT_NAMES = options.DEFAULT_NAMES + ('in_db',)

class MyModel(models.Model):
  class Meta:
    in_db = 'edlserver'

然后需要一个数据库路由器:

class DatabaseMetaRouter(object):
def db_for_read(self, model, **hints):
    """
    Route to the given in_db database in Meta
    """
    if hasattr(model._meta, 'in_db'):
        return model._meta.in_db
    else:
        return 'default'

def db_for_write(self, model, **hints):
    """
    Route to the given in_db database in Meta
    """
    if hasattr(model._meta, 'in_db'):
        return model._meta.in_db
    else:
        return 'default'

def allow_relation(self, obj1, obj2, **hints):
    """
    Always allow
    """
    return True

def allow_migrate(self, db, app_label, model_name=None, **hints):
    """
    Always allow
    """
    return True

【讨论】:

  • 好的。这仍然会产生完全相同的错误:psycopg2.ProgrammingError: relation "jobs" does not exist LINE 1: ...bs"."time_end", "jobs"."pid", "jobs"."title" FROM "jobs" WHE...。但是,如果我使用.using('edlserver') 手动遵循关系,我会得到正确的值。所以我得出结论,这真的是 django 不知道要使用什么数据库。如果我手动操作,速度会有所不同吗?
【解决方案2】:

问题是这里的obj是Logs的一个实例

def get_job_name(self, obj):
   return obj['id_job__id_job_type__name']

Django 模型看起来像字典,闻起来像字典,但它们不是字典。正确的用法是:

  return obj.job.job_type.name

我建议你打开一个 django shell,加载一个而不是 Logs,然后使用 help() 命令并试验路径。

至于第二个问题,查询使用了错误的数据库,您需要在查询中定义database router 或添加using()

【讨论】:

  • 我刚试过。 AttributeError: 'Logs' object has no attribute 'id_job__id_job_type__name' 是结果。我也试过return obj.job.job_type.name,结果是psycopg2.ProgrammingError: relation "jobs" does not exist LINE 1: ...bs"."time_end", "jobs"."pid", "jobs"."title" FROM "jobs" WHE...。我在项目的另一个地方有虚线符号,它适用于 auth 模型(在默认数据库中)。
  • 我投票 +1。但我也被否决了。对不起。我真的很高兴你能提供帮助。
  • @e4c5 不要假设。我投了反对票,因为你的答案是错误的,并且把 OP 放在了错误的轨道上。模型实例上的以下关系使用简单的属性访问,只有查询查找使用双下划线语法。
  • @knbk 好点,我在这里混淆了查询和实例,但是在您投票时,我只是复制粘贴了 OP 的代码,原始答案解决了原始问题。
  • using() 优先
猜你喜欢
  • 2013-02-24
  • 2019-09-23
  • 1970-01-01
  • 2013-05-08
  • 1970-01-01
  • 2014-01-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多