2017-04-10 51 views
3

我的Django应用程序有一些类别,我存储在Category模型中。我经常在代码中引用它们,所以我发现有一个带有引用(“常量”)的模块可用于这些类别和它们的组合,所以错别字将会很快失败。这也提供了缓存的好处。最后,它是实际的模型,因此它具有所有相关的功能。它看起来像这样:如何避免在Django中导入时间数据库访问?

def load_category(name): 
    return Category.objects.get(name=name) 

DOGS = load_category("dogs") 
CATS = load_category("cats") 

但是,这会导致导入时访问数据库并导致各种问题。在添加一个类似这样的引用的新类别后,我必须在./manage.py发挥作用之前运行数据迁移。在切换到使用Django的测试框架时,我碰到一个新问题,就是这些测试框架是从默认(例如dev或prod)数据库加载的,而不是在this warning中明确提到的测试框架。

如果你的代码试图当它的模块 编译访问数据库,设置之前测试数据库时,与 潜在的意想不到的结果,这将发生。例如,如果您在模块级代码中存在数据库 查询并且存在真实数据库,则生产数据 可能会污染您的测试。无论如何在代码中有这样的进口时间 数据库查询是一个糟糕的主意 - 重写你的代码,使其 不这样做。

在避免导入时访问数据库的同时获得这些引用的好处的最佳模式是什么?

一种可能的解决方案是一种代理模式,它返回一个伪类别,它转发所有模型的功能,但在必要时才访问数据库。我想看看其他人如何用这种方法或其他解决方案解决这个问题。

(相关但不同的问题:Django test. Finding data from your production database when running tests?

最后进场

该方法通过@凯文 - 克里斯托弗 - 亨利很适合我的工作。但是,除了修正这些声明的引用外,我还必须延迟对其他代码的引用的访问。在这里我发现两种方法很有用。我发现Python Lazy Object Proxy。这个简单的对象将工厂函数作为输入,它被懒惰地执行以产生被包装的对象。

MAP_OF_THINGS = Proxy(lambda: { 
     DOG: ... 
     CAT: ... 
}) 

完成同样的事情正在推动代码到装饰有memoize所以他们会只执行一次工厂功能的类似的方式。

注:我最初尝试使用上面的代理对象作为直接解决我的懒惰访问模型对象的问题。然而,尽管是非常好的仿制品,这些对象查询和过滤的,当我:

TypeError: 'Category' object is not callable 

果然,Proxy回报Truecallable(即使文件说这并不能保证它的调用)。看起来Django查询太聪明了,一定会发现一些与虚拟模型不兼容的东西。

对于您的应用程序,Proxy可能已经足够好了。

回答

2

我自己遇到同样的问题,并同意在这里有一些最佳实践会很好。

我结束了基于该descriptor protocol的方法:

class LazyInstance: 
    def __init__(self, *args, **kwargs): 
     self.args = args 
     self.kwargs = kwargs 
     self.instance = None 

    def __get__(self, obj, cls): 
     if self.instance is None: 
      self.instance, _ = cls.objects.get_or_create(*self.args, **self.kwargs) 

     return self.instance 

然后在我的模型类,我有一些特殊对象:

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

    DOGS = LazyInstance(name="dogs") 
    CATS = LazyInstance(name="cats") 

所以什么也没有发生在导入时。第一次访问特殊对象时,查找相关实例(并在必要时创建)并进行缓存。

+0

谢谢。这似乎不支持方法调用,但添加__getattr__修复该问题。然而,仍然有平等的问题,所以我想我需要处理特殊的方法? http://code.activestate.com/recipes/496741-object-proxying/ –

+0

@JohnLehmann:我不确定你的意思是支持方法调用。这不是使用代理对象;当你访问'Category.DOGS'时,你会得到一个常规的Django模型实例。 –

+0

知道了!顺便说一句,你的'LazyInstance'需要从'object'继承,否则它会静默失败。感谢你的回答。 –

1

由于无法覆盖其访问函数,因此使用模块级变量的用处不大。但是,您可以通过__getattribute__为类和实例变量执行此操作。您可以使用到懒洋洋加载类别:

class Categories(object): 
    _categories = {'DOGS': 'dogs', 'CATS': 'cats'} 
    def __getattribute__(self, key): 
     try: 
      return super(Categories, self).__getattribute__(key) 
     except AttributeError: 
      pass 
     try: 
      value = load_category(self._categories[key]) 
     except KeyError: 
      raise AttributeError(key) 
     setattr(self, key, value) 
     return value 

Categories = Categories() # Shadow class with singleton instance 

相反module.DOGS,你会再使用module.Categories.DOGS。在第一次访问时,该类别被加载并存储以供将来查找。