标签归档:Django

如何将Ajax与Django应用程序集成?

问题:如何将Ajax与Django应用程序集成?

我是Django的新手,而Ajax的新手。我正在一个需要将两者整合的项目中。我相信我理解它们背后的原理,但是并没有找到对两者的良好解释。

有人可以给我一个简短的解释,说明如何将两者集成在一起才能更改代码库?

例如,我仍然可以将HttpResponseAjax与一起使用,还是必须随着Ajax的使用而改变我的回答?如果是这样,请您举例说明如何更改请求的响应?如果有什么不同,我返回的数据是JSON。

I am new to Django and pretty new to Ajax. I am working on a project where I need to integrate the two. I believe that I understand the principles behind them both, but have not found a good explanation of the two together.

Could someone give me a quick explanation of how the codebase must change with the two of them integrating together?

For example, can I still use the HttpResponse with Ajax, or do my responses have to change with the use of Ajax? If so, could you please provide an example of how the responses to the requests must change? If it makes any difference, the data I am returning is JSON.


回答 0

即使这不完全符合SO精神,但我还是很喜欢这个问题,因为刚开始时我遇到了同样的麻烦,因此我将为您提供快速指南。显然,您不了解其背后的原理(不要将其视为冒犯,但如果您这样做,您将不会提出疑问)。

Django是服务器端的。这就意味着,比如说客户访问一个URL,您内部就有一个函数views,该函数可以呈现他所看到的内容并以HTML返回响应。让我们将其分解为示例:

views.py:

def hello(request):
    return HttpResponse('Hello World!')

def home(request):
    return render_to_response('index.html', {'variable': 'world'})

index.html:

<h1>Hello {{ variable }}, welcome to my awesome site</h1>

urls.py:

url(r'^hello/', 'myapp.views.hello'),
url(r'^home/', 'myapp.views.home'),

这是最简单的用法示例。转到127.0.0.1:8000/hello表示对hello()函数的请求,转到127.0.0.1:8000/home将返回index.html并按要求替换所有变量(您现在可能已经知道了这一切)。

现在让我们谈谈AJAX。AJAX调用是执行异步请求的客户端代码。这听起来很复杂,但这仅意味着它会在后台为您提出请求,然后处理响应。因此,当您对某个URL进行AJAX调用时,您将获得与访问该位置的用户相同的数据。

例如,对的AJAX调用127.0.0.1:8000/hello将返回与您访问它相同的东西。只有这一次,您才将其包含在JavaScript函数中,并且可以根据需要进行处理。让我们看一个简单的用例:

$.ajax({
    url: '127.0.0.1:8000/hello',
    type: 'get', // This is the default though, you don't actually need to always mention it
    success: function(data) {
        alert(data);
    },
    failure: function(data) { 
        alert('Got an error dude');
    }
}); 

一般过程是这样的:

  1. 127.0.0.1:8000/hello就像您打开一个新标签页并自己完成一样,呼叫将转到URL 。
  2. 如果成功(状态码200),请执行成功功能,这将提醒收到的数据。
  3. 如果失败,请执行其他功能。

现在在这里会发生什么?您会收到有关“ hello world”的警报。如果您拨打AJAX到家里怎么办?同样,您会收到警告,说明<h1>Hello world, welcome to my awesome site</h1>

换句话说-AJAX调用没有新内容。它们只是让您在不离开页面的情况下让用户获取数据和信息的一种方法,它使您的网站设计流畅而整洁。您应该注意一些准则:

  1. 学习jQuery。我不能太强调这一点。您将需要稍微了解一下才能知道如何处理收到的数据。您还需要了解一些基本的JavaScript语法(与python相距不远,您将习惯它)。我强烈推荐Envato的jQuery视频教程,它们很棒,将使您走上正确的道路。
  2. 何时使用JSON?。您将看到很多示例,其中Django视图发送的数据使用JSON。我没有进入细节上,因为它不是重要的是如何做到这一点(有很多的解释,比比皆是)和很多更重要的时候。答案是-JSON数据是序列化数据。也就是说,您可以操纵的数据。就像我提到的,AJAX调用将获取响应,就好像用户自己做了一样。现在说您不想弄乱所有html,而是想发送数据(也许是对象列表)。JSON对此很有好处,因为它作为对象发送(JSON数据看起来像python字典),然后您可以对其进行迭代或执行其他操作,从而无需筛选无用的html。
  3. 最后添加。当您构建Web应用程序并想要实现AJAX时-帮个忙。首先,构建完全没有任何AJAX的应用程序。看到一切正常。然后,直到那时,才开始编写AJAX调用。这是一个很好的过程,可以帮助您学习很多东西。
  4. 使用chrome的开发人员工具。由于AJAX调用是在后台完成的,因此有时很难调试它们。您应该使用chrome开发人员工具(或类似的工具,例如firebug)和console.log要调试的东西。我不会详细解释,只是在Google周围寻找它。这对您会非常有帮助。
  5. CSRF意识。最后,请记住Django中的发布请求需要csrf_token。通过AJAX调用,很多时候您想发送数据而不刷新页面。您可能会遇到一些麻烦,最后才想起那个-等待,您忘记了发送csrf_token。这是AJAX-Django集成中的一个已知的初学者障碍,但是当您学习了如何使其变得更好时,它就像馅饼一样容易。

这就是我想到的一切。这是一个广泛的主题,但是,可能没有足够的例子。只要按照自己的方式做,就慢慢地,最终会得到它。

Even though this isn’t entirely in the SO spirit, I love this question, because I had the same trouble when I started, so I’ll give you a quick guide. Obviously you don’t understand the principles behind them (don’t take it as an offense, but if you did you wouldn’t be asking).

Django is server-side. It means, say a client goes to a URL, you have a function inside views that renders what he sees and returns a response in HTML. Let’s break it up into examples:

views.py:

def hello(request):
    return HttpResponse('Hello World!')

def home(request):
    return render_to_response('index.html', {'variable': 'world'})

index.html:

<h1>Hello {{ variable }}, welcome to my awesome site</h1>

urls.py:

url(r'^hello/', 'myapp.views.hello'),
url(r'^home/', 'myapp.views.home'),

That’s an example of the simplest of usages. Going to 127.0.0.1:8000/hello means a request to the hello() function, going to 127.0.0.1:8000/home will return the index.html and replace all the variables as asked (you probably know all this by now).

Now let’s talk about AJAX. AJAX calls are client-side code that does asynchronous requests. That sounds complicated, but it simply means it does a request for you in the background and then handles the response. So when you do an AJAX call for some URL, you get the same data you would get as a user going to that place.

For example, an AJAX call to 127.0.0.1:8000/hello will return the same thing it would as if you visited it. Only this time, you have it inside a JavaScript function and you can deal with it however you’d like. Let’s look at a simple use case:

$.ajax({
    url: '127.0.0.1:8000/hello',
    type: 'get', // This is the default though, you don't actually need to always mention it
    success: function(data) {
        alert(data);
    },
    failure: function(data) { 
        alert('Got an error dude');
    }
}); 

The general process is this:

  1. The call goes to the URL 127.0.0.1:8000/hello as if you opened a new tab and did it yourself.
  2. If it succeeds (status code 200), do the function for success, which will alert the data received.
  3. If fails, do a different function.

Now what would happen here? You would get an alert with ‘hello world’ in it. What happens if you do an AJAX call to home? Same thing, you’ll get an alert stating <h1>Hello world, welcome to my awesome site</h1>.

In other words – there’s nothing new about AJAX calls. They are just a way for you to let the user get data and information without leaving the page, and it makes for a smooth and very neat design of your website. A few guidelines you should take note of:

  1. Learn jQuery. I cannot stress this enough. You’re gonna have to understand it a little to know how to handle the data you receive. You’ll also need to understand some basic JavaScript syntax (not far from python, you’ll get used to it). I strongly recommend Envato’s video tutorials for jQuery, they are great and will put you on the right path.
  2. When to use JSON?. You’re going to see a lot of examples where the data sent by the Django views is in JSON. I didn’t go into detail on that, because it isn’t important how to do it (there are plenty of explanations abound) and a lot more important when. And the answer to that is – JSON data is serialized data. That is, data you can manipulate. Like I mentioned, an AJAX call will fetch the response as if the user did it himself. Now say you don’t want to mess with all the html, and instead want to send data (a list of objects perhaps). JSON is good for this, because it sends it as an object (JSON data looks like a python dictionary), and then you can iterate over it or do something else that removes the need to sift through useless html.
  3. Add it last. When you build a web app and want to implement AJAX – do yourself a favor. First, build the entire app completely devoid of any AJAX. See that everything is working. Then, and only then, start writing the AJAX calls. That’s a good process that helps you learn a lot as well.
  4. Use chrome’s developer tools. Since AJAX calls are done in the background it’s sometimes very hard to debug them. You should use the chrome developer tools (or similar tools such as firebug) and console.log things to debug. I won’t explain in detail, just google around and find out about it. It would be very helpful to you.
  5. CSRF awareness. Finally, remember that post requests in Django require the csrf_token. With AJAX calls, a lot of times you’d like to send data without refreshing the page. You’ll probably face some trouble before you’d finally remember that – wait, you forgot to send the csrf_token. This is a known beginner roadblock in AJAX-Django integration, but after you learn how to make it play nice, it’s easy as pie.

That’s everything that comes to my head. It’s a vast subject, but yeah, there’s probably not enough examples out there. Just work your way there, slowly, you’ll get it eventually.


回答 1

除了yuvi的出色答案之外,我想添加一个有关如何在Django中处理此问题的小示例(除了将要使用的所有js)。该示例使用AjaxableResponseMixin并假设一个Author模型。

import json

from django.http import HttpResponse
from django.views.generic.edit import CreateView
from myapp.models import Author

class AjaxableResponseMixin(object):
    """
    Mixin to add AJAX support to a form.
    Must be used with an object-based FormView (e.g. CreateView)
    """
    def render_to_json_response(self, context, **response_kwargs):
        data = json.dumps(context)
        response_kwargs['content_type'] = 'application/json'
        return HttpResponse(data, **response_kwargs)

    def form_invalid(self, form):
        response = super(AjaxableResponseMixin, self).form_invalid(form)
        if self.request.is_ajax():
            return self.render_to_json_response(form.errors, status=400)
        else:
            return response

    def form_valid(self, form):
        # We make sure to call the parent's form_valid() method because
        # it might do some processing (in the case of CreateView, it will
        # call form.save() for example).
        response = super(AjaxableResponseMixin, self).form_valid(form)
        if self.request.is_ajax():
            data = {
                'pk': self.object.pk,
            }
            return self.render_to_json_response(data)
        else:
            return response

class AuthorCreate(AjaxableResponseMixin, CreateView):
    model = Author
    fields = ['name']

来源:Django文档,具有基于类的视图的表单处理

Django 1.6版的链接不再可用,已更新至1.11版

Further from yuvi’s excellent answer, I would like to add a small specific example on how to deal with this within Django (beyond any js that will be used). The example uses AjaxableResponseMixin and assumes an Author model.

import json

from django.http import HttpResponse
from django.views.generic.edit import CreateView
from myapp.models import Author

class AjaxableResponseMixin(object):
    """
    Mixin to add AJAX support to a form.
    Must be used with an object-based FormView (e.g. CreateView)
    """
    def render_to_json_response(self, context, **response_kwargs):
        data = json.dumps(context)
        response_kwargs['content_type'] = 'application/json'
        return HttpResponse(data, **response_kwargs)

    def form_invalid(self, form):
        response = super(AjaxableResponseMixin, self).form_invalid(form)
        if self.request.is_ajax():
            return self.render_to_json_response(form.errors, status=400)
        else:
            return response

    def form_valid(self, form):
        # We make sure to call the parent's form_valid() method because
        # it might do some processing (in the case of CreateView, it will
        # call form.save() for example).
        response = super(AjaxableResponseMixin, self).form_valid(form)
        if self.request.is_ajax():
            data = {
                'pk': self.object.pk,
            }
            return self.render_to_json_response(data)
        else:
            return response

class AuthorCreate(AjaxableResponseMixin, CreateView):
    model = Author
    fields = ['name']

Source: Django documentation, Form handling with class-based views

The link to version 1.6 of Django is no longer available updated to version 1.11


回答 2

我写这封信是因为可接受的答案很旧,需要复习。

所以这就是我在2019年将Ajax与Django集成的方式:)让我们举一个实际的例子说明何时需要Ajax:-

可以说我有一个带有注册用户名的模型,并借助于Ajax我想知道给定的用户名是否存在。

的HTML:

<p id="response_msg"></p> 
<form id="username_exists_form" method='GET'>
      Name: <input type="username" name="username" />
      <button type='submit'> Check </button>           
</form>   

阿贾克斯:

$('#username_exists_form').on('submit',function(e){
    e.preventDefault();
    var username = $(this).find('input').val();
    $.get('/exists/',
          {'username': username},   
          function(response){ $('#response_msg').text(response.msg); }
    );
}); 

urls.py:

from django.contrib import admin
from django.urls import path
from . import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('exists/', views.username_exists, name='exists'),
]

views.py:

def username_exists(request):
    data = {'msg':''}   
    if request.method == 'GET':
        username = request.GET.get('username').lower()
        exists = Usernames.objects.filter(name=username).exists()
        if exists:
            data['msg'] = username + ' already exists.'
        else:
            data['msg'] = username + ' does not exists.'
    return JsonResponse(data)

同样不推荐使用的render_to_response已被render取代,从Django 1.7起,而不是HttpResponse,我们使用JsonResponse进行Ajax响应。因为它带有JSON编码器,所以您不需要在返回响应对象之前对数据进行序列化,但HttpResponse不建议使用。

I am writing this because the accepted answer is pretty old, it needs a refresher.

So this is how I would integrate Ajax with Django in 2019 :) And lets take a real example of when we would need Ajax :-

Lets say I have a model with registered usernames and with the help of Ajax I wanna know if a given username exists.

html:

<p id="response_msg"></p> 
<form id="username_exists_form" method='GET'>
      Name: <input type="username" name="username" />
      <button type='submit'> Check </button>           
</form>   

ajax:

$('#username_exists_form').on('submit',function(e){
    e.preventDefault();
    var username = $(this).find('input').val();
    $.get('/exists/',
          {'username': username},   
          function(response){ $('#response_msg').text(response.msg); }
    );
}); 

urls.py:

from django.contrib import admin
from django.urls import path
from . import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('exists/', views.username_exists, name='exists'),
]

views.py:

def username_exists(request):
    data = {'msg':''}   
    if request.method == 'GET':
        username = request.GET.get('username').lower()
        exists = Usernames.objects.filter(name=username).exists()
        if exists:
            data['msg'] = username + ' already exists.'
        else:
            data['msg'] = username + ' does not exists.'
    return JsonResponse(data)

Also render_to_response which is deprecated and has been replaced by render and from Django 1.7 onwards instead of HttpResponse we use JsonResponse for ajax response. Because it comes with a JSON encoder, so you don’t need to serialize the data before returning the response object but HttpResponse is not deprecated.


回答 3

简单而漂亮。您无需更改视图。Bjax处理所有链接。看看这个: Bjax

用法:

<script src="bjax.min.js" type="text/javascript"></script>
<link href="bjax.min.css" rel="stylesheet" type="text/css" />

最后,将其包含在html的HEAD中:

$('a').bjax();

有关更多设置,请在此处签出演示: Bjax演示

Simple and Nice. You don’t have to change your views. Bjax handles all your links. Check this out: Bjax

Usage:

<script src="bjax.min.js" type="text/javascript"></script>
<link href="bjax.min.css" rel="stylesheet" type="text/css" />

Finally, include this in the HEAD of your html:

$('a').bjax();

For more settings, checkout demo here: Bjax Demo


回答 4

AJAX是执行异步任务的最佳方法。在任何网站建设中,进行异步调用都是很常见的事情。我们将以一个简短的例子来学习如何在Django中实现AJAX。我们需要使用jQuery,以便减少JavaScript。

这是Contact示例,这是最简单的示例,我用它来解释AJAX的基本知识及其在Django中的实现。在此示例中,我们将发出POST请求。我正在关注本文的示例之一:https : //djangopy.org/learn/step-up-guide-to-implement-ajax-in-django

models.py

首先,创建具有基本细节的Contact模型。

from django.db import models

class Contact(models.Model):
    name = models.CharField(max_length = 100)
    email = models.EmailField()
    message = models.TextField()
    timestamp = models.DateTimeField(auto_now_add = True)

    def __str__(self):
        return self.name

表格

创建上述模型的表单。

from django import forms
from .models import Contact

class ContactForm(forms.ModelForm):
    class Meta:
        model = Contact
        exclude = ["timestamp", ]

views.py

这些视图看起来类似于基于功能的基本创建视图,但是我们不使用render来返回,而是使用JsonResponse响应。

from django.http import JsonResponse
from .forms import ContactForm

def postContact(request):
    if request.method == "POST" and request.is_ajax():
        form = ContactForm(request.POST)
        form.save()
        return JsonResponse({"success":True}, status=200)
    return JsonResponse({"success":False}, status=400)

urls.py

让我们创建以上视图的路线。

from django.contrib import admin
from django.urls import path
from app_1 import views as app1

urlpatterns = [
    path('ajax/contact', app1.postContact, name ='contact_submit'),
]

模板

移至前端部分,呈现在封闭表单标签上方创建的表单以及csrf_token和Submit按钮。请注意,我们已经包含了jquery库。

<form id = "contactForm" method= "POST">{% csrf_token %}
   {{ contactForm.as_p }}
  <input type="submit" name="contact-submit" class="btn btn-primary" />
</form>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Java脚本

现在让我们谈谈javascript部分,在表单提交中,我们发出POST类型的ajax请求,获取表单数据并发送到服务器端。

$("#contactForm").submit(function(e){
    // prevent from normal form behaviour
        e.preventDefault();
        // serialize the form data  
        var serializedData = $(this).serialize();
        $.ajax({
            type : 'POST',
            url :  "{% url 'contact_submit' %}",
            data : serializedData,
            success : function(response){
            //reset the form after successful submit
                $("#contactForm")[0].reset(); 
            },
            error : function(response){
                console.log(response)
            }
        });
   });

这只是使用Django进行AJAX入门的基本示例,如果您想了解更多示例,可以阅读本文:https : //djangopy.org/learn/step-up-guide-to-在Django中实现ajax

AJAX is the best way to do asynchronous tasks. Making asynchronous calls is something common in use in any website building. We will take a short example to learn how we can implement AJAX in Django. We need to use jQuery so as to write less javascript.

This is Contact example, which is the simplest example, I am using to explain the basics of AJAX and its implementation in Django. We will be making POST request in this example. I am following one of the example of this post: https://djangopy.org/learn/step-up-guide-to-implement-ajax-in-django

models.py

Let’s first create the model of Contact, having basic details.

from django.db import models

class Contact(models.Model):
    name = models.CharField(max_length = 100)
    email = models.EmailField()
    message = models.TextField()
    timestamp = models.DateTimeField(auto_now_add = True)

    def __str__(self):
        return self.name

forms.py

Create the form for the above model.

from django import forms
from .models import Contact

class ContactForm(forms.ModelForm):
    class Meta:
        model = Contact
        exclude = ["timestamp", ]

views.py

The views look similar to the basic function-based create view, but instead of returning with render, we are using JsonResponse response.

from django.http import JsonResponse
from .forms import ContactForm

def postContact(request):
    if request.method == "POST" and request.is_ajax():
        form = ContactForm(request.POST)
        form.save()
        return JsonResponse({"success":True}, status=200)
    return JsonResponse({"success":False}, status=400)

urls.py

Let’s create the route of the above view.

from django.contrib import admin
from django.urls import path
from app_1 import views as app1

urlpatterns = [
    path('ajax/contact', app1.postContact, name ='contact_submit'),
]

template

Moving to frontend section, render the form which was created above enclosing form tag along with csrf_token and submit button. Note that we have included the jquery library.

<form id = "contactForm" method= "POST">{% csrf_token %}
   {{ contactForm.as_p }}
  <input type="submit" name="contact-submit" class="btn btn-primary" />
</form>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Javascript

Let’s now talk about javascript part, on the form submit we are making ajax request of type POST, taking the form data and sending to the server side.

$("#contactForm").submit(function(e){
    // prevent from normal form behaviour
        e.preventDefault();
        // serialize the form data  
        var serializedData = $(this).serialize();
        $.ajax({
            type : 'POST',
            url :  "{% url 'contact_submit' %}",
            data : serializedData,
            success : function(response){
            //reset the form after successful submit
                $("#contactForm")[0].reset(); 
            },
            error : function(response){
                console.log(response)
            }
        });
   });

This is just a basic example to get started with AJAX with django, if you want to get dive with several more examples, you can go through this article: https://djangopy.org/learn/step-up-guide-to-implement-ajax-in-django


回答 5

我尝试在项目中使用AjaxableResponseMixin,但最终出现以下错误消息:

配置不正确:没有要重定向到的URL。在模型上提供一个URL或定义一个get_absolute_url方法。

这是因为当您向浏览器发送JSON请求时,CreateView将返回重定向响应,而不是返回HttpResponse。因此,我对进行了一些更改AjaxableResponseMixin。如果该请求是ajax请求,则不会调用该super.form_valid方法,而只是form.save()直接调用。

from django.http import JsonResponse
from django import forms
from django.db import models

class AjaxableResponseMixin(object):
    success_return_code = 1
    error_return_code = 0
    """
    Mixin to add AJAX support to a form.
    Must be used with an object-based FormView (e.g. CreateView)
    """
    def form_invalid(self, form):
        response = super(AjaxableResponseMixin, self).form_invalid(form)
        if self.request.is_ajax():
            form.errors.update({'result': self.error_return_code})
            return JsonResponse(form.errors, status=400)
        else:
            return response

    def form_valid(self, form):
        # We make sure to call the parent's form_valid() method because
        # it might do some processing (in the case of CreateView, it will
        # call form.save() for example).
        if self.request.is_ajax():
            self.object = form.save()
            data = {
                'result': self.success_return_code
            }
            return JsonResponse(data)
        else:
            response = super(AjaxableResponseMixin, self).form_valid(form)
            return response

class Product(models.Model):
    name = models.CharField('product name', max_length=255)

class ProductAddForm(forms.ModelForm):
    '''
    Product add form
    '''
    class Meta:
        model = Product
        exclude = ['id']


class PriceUnitAddView(AjaxableResponseMixin, CreateView):
    '''
    Product add view
    '''
    model = Product
    form_class = ProductAddForm

I have tried to use AjaxableResponseMixin in my project, but had ended up with the following error message:

ImproperlyConfigured: No URL to redirect to. Either provide a url or define a get_absolute_url method on the Model.

That is because the CreateView will return a redirect response instead of returning a HttpResponse when you to send JSON request to the browser. So I have made some changes to the AjaxableResponseMixin. If the request is an ajax request, it will not call the super.form_valid method, just call the form.save() directly.

from django.http import JsonResponse
from django import forms
from django.db import models

class AjaxableResponseMixin(object):
    success_return_code = 1
    error_return_code = 0
    """
    Mixin to add AJAX support to a form.
    Must be used with an object-based FormView (e.g. CreateView)
    """
    def form_invalid(self, form):
        response = super(AjaxableResponseMixin, self).form_invalid(form)
        if self.request.is_ajax():
            form.errors.update({'result': self.error_return_code})
            return JsonResponse(form.errors, status=400)
        else:
            return response

    def form_valid(self, form):
        # We make sure to call the parent's form_valid() method because
        # it might do some processing (in the case of CreateView, it will
        # call form.save() for example).
        if self.request.is_ajax():
            self.object = form.save()
            data = {
                'result': self.success_return_code
            }
            return JsonResponse(data)
        else:
            response = super(AjaxableResponseMixin, self).form_valid(form)
            return response

class Product(models.Model):
    name = models.CharField('product name', max_length=255)

class ProductAddForm(forms.ModelForm):
    '''
    Product add form
    '''
    class Meta:
        model = Product
        exclude = ['id']


class PriceUnitAddView(AjaxableResponseMixin, CreateView):
    '''
    Product add view
    '''
    model = Product
    form_class = ProductAddForm

回答 6

当我们使用Django时:

Server ===> Client(Browser)   
      Send a page

When you click button and send the form,
----------------------------
Server <=== Client(Browser)  
      Give data back. (data in form will be lost)
Server ===> Client(Browser)  
      Send a page after doing sth with these data
----------------------------

如果要保留旧数据,则可以不使用Ajax。(页面将刷新)

Server ===> Client(Browser)   
      Send a page
Server <=== Client(Browser)  
      Give data back. (data in form will be lost)
Server ===> Client(Browser)  
      1. Send a page after doing sth with data
      2. Insert data into form and make it like before. 
      After these thing, server will send a html page to client. It means that server do more work, however, the way to work is same.

或者您可以使用Ajax(页面不会刷新)

--------------------------
<Initialization> 
Server ===> Client(Browser) [from URL1]    
      Give a page                      
--------------------------  
<Communication>
Server <=== Client(Browser)     
      Give data struct back but not to refresh the page.
Server ===> Client(Browser) [from URL2] 
      Give a data struct(such as JSON)
---------------------------------

如果使用Ajax,则必须执行以下操作:

  1. 使用URL1初始化HTML页面(我们通常使用Django模板初始化页面)。然后服务器向客户端发送一个html页面。
  2. 使用Ajax通过URL2与服务器通信。然后服务器向客户端发送数据结构。

Django与Ajax不同。原因如下:

  • 返回给客户的东西是不同的。Django的情况是HTML页面。Ajax的情况是数据结构。 
  • Django擅长创建某些东西,但是只能创建一次,不能更改任何东西。Django就像动漫,由许多图片组成。相比之下,Ajax并不擅长于在现有的html页面中创建某事,但擅长于更改某事。

我认为,如果您想在任何地方使用ajax。当您首先需要使用数据初始化页面时,可以将Django与Ajax结合使用。但是在某些情况下,您只需要一个静态页面,而服务器上没有任何内容,则不需要使用Django模板。

如果您不认为Ajax是最佳实践。您可以使用Django模板来完成所有操作,例如动漫。

(我英文不太好)

When we use Django:

Server ===> Client(Browser)   
      Send a page

When you click button and send the form,
----------------------------
Server <=== Client(Browser)  
      Give data back. (data in form will be lost)
Server ===> Client(Browser)  
      Send a page after doing sth with these data
----------------------------

If you want to keep old data, you can do it without Ajax. (Page will be refreshed)

Server ===> Client(Browser)   
      Send a page
Server <=== Client(Browser)  
      Give data back. (data in form will be lost)
Server ===> Client(Browser)  
      1. Send a page after doing sth with data
      2. Insert data into form and make it like before. 
      After these thing, server will send a html page to client. It means that server do more work, however, the way to work is same.

Or you can do with Ajax (Page will be not refreshed)

--------------------------
<Initialization> 
Server ===> Client(Browser) [from URL1]    
      Give a page                      
--------------------------  
<Communication>
Server <=== Client(Browser)     
      Give data struct back but not to refresh the page.
Server ===> Client(Browser) [from URL2] 
      Give a data struct(such as JSON)
---------------------------------

If you use Ajax, you must do these:

  1. Initial a HTML page using URL1 (we usually initial page by Django template). And then server send client a html page.
  2. Use Ajax to communicate with server using URL2. And then server send client a data struct.

Django is different from Ajax. The reason for this is as follows:

  • The thing return to client is different. The case of Django is HTML page. The case of Ajax is data struct. 
  • Django is good at creating something, but it only can create once, it cannot change anything. Django is like anime, consist of many picture. By contrast, Ajax is not good at creating sth but good at change sth in exist html page.

In my opinion, if you would like to use ajax everywhere. when you need to initial a page with data at first, you can use Django with Ajax. But in some case, you just need a static page without anything from server, you need not use Django template.

If you don’t think Ajax is the best practice. you can use Django template to do everything, like anime.

(My English is not good)


如何克隆Django模型实例对象并将其保存到数据库?

问题:如何克隆Django模型实例对象并将其保存到数据库?

Foo.objects.get(pk="foo")
<Foo: test>

在数据库中,我想添加另一个对象,它是上述对象的副本。

假设我的桌子有一排。我想用不同的主键将第一行对象插入另一行。我怎样才能做到这一点?

Foo.objects.get(pk="foo")
<Foo: test>

In the database, I want to add another object which is a copy of the object above.

Suppose my table has one row. I want to insert the first row object into another row with a different primary key. How can I do that?


回答 0

只需更改对象的主键并运行save()。

obj = Foo.objects.get(pk=<some_existing_pk>)
obj.pk = None
obj.save()

如果要自动生成密钥,请将新密钥设置为“无”。

有关UPDATE / INSERT的更多信息,请点击这里

有关复制模型实例的官方文档:https : //docs.djangoproject.com/en/2.2/topics/db/queries/#copying-model-instances

Just change the primary key of your object and run save().

obj = Foo.objects.get(pk=<some_existing_pk>)
obj.pk = None
obj.save()

If you want auto-generated key, set the new key to None.

More on UPDATE/INSERT here.

Official docs on copying model instances: https://docs.djangoproject.com/en/2.2/topics/db/queries/#copying-model-instances


回答 1

Django数据库查询文档包括有关复制模型实例的部分。假设您的主键是自动生成的,则得到要复制的对象,将主键设置为None,然后再次保存该对象:

blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1

blog.pk = None
blog.save() # blog.pk == 2

在此片段中,第一个save()创建原始对象,第二个save()创建副本。

如果您继续阅读文档,那么还会有一些示例,说明如何处理两种更复杂的情况:(1)复制一个对象,该对象是模型子类的一个实例,(2)还复制相关对象,包括多对多对象-很多关系。


请注意miah的答案:miah的答案None中提到了将pk设置为,尽管未将其设置在前面和中间。因此,我的回答主要是为了强调该方法,这是Django推荐的方法。

历史记录:Django文档直到1.4版才对此进行了解释。但是从1.4之前开始就有可能。

将来可能的功能:对该票证进行了上述文档更改。在故障单的注释线程上,还讨论了copy为模型类添加内置函数的问题,但据我所知,他们决定不解决该问题。因此,这种“手动”复制方式现在可能必须要做。

The Django documentation for database queries includes a section on copying model instances. Assuming your primary keys are autogenerated, you get the object you want to copy, set the primary key to None, and save the object again:

blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1

blog.pk = None
blog.save() # blog.pk == 2

In this snippet, the first save() creates the original object, and the second save() creates the copy.

If you keep reading the documentation, there are also examples on how to handle two more complex cases: (1) copying an object which is an instance of a model subclass, and (2) also copying related objects, including objects in many-to-many relations.


Note on miah’s answer: Setting the pk to None is mentioned in miah’s answer, although it’s not presented front and center. So my answer mainly serves to emphasize that method as the Django-recommended way to do it.

Historical note: This wasn’t explained in the Django docs until version 1.4. It has been possible since before 1.4, though.

Possible future functionality: The aforementioned docs change was made in this ticket. On the ticket’s comment thread, there was also some discussion on adding a built-in copy function for model classes, but as far as I know they decided not to tackle that problem yet. So this “manual” way of copying will probably have to do for now.


回答 2

小心点 如果您处于某种循环中,并且要一个接一个地检索对象,这可能会非常昂贵。如果您不想调用数据库,请执行以下操作:

from copy import deepcopy

new_instance = deepcopy(object_you_want_copied)
new_instance.id = None
new_instance.save()

它与其他一些答案具有相同的作用,但是不会进行数据库调用来检索对象。如果您要复制数据库中尚不存在的对象,这也很有用。

Be careful here. This can be extremely expensive if you’re in a loop of some kind and you’re retrieving objects one by one. If you don’t want the call to the database, just do:

from copy import deepcopy

new_instance = deepcopy(object_you_want_copied)
new_instance.id = None
new_instance.save()

It does the same thing as some of these other answers, but it doesn’t make the database call to retrieve an object. This is also useful if you want to make a copy of an object that doesn’t exist yet in the database.


回答 3

使用以下代码:

from django.forms import model_to_dict

instance = Some.objects.get(slug='something')

kwargs = model_to_dict(instance, exclude=['id'])
new_instance = Some.objects.create(**kwargs)

Use the below code :

from django.forms import model_to_dict

instance = Some.objects.get(slug='something')

kwargs = model_to_dict(instance, exclude=['id'])
new_instance = Some.objects.create(**kwargs)

回答 4

有一个克隆片段在这里,你可以添加到您的模型,做到这一点:

def clone(self):
  new_kwargs = dict([(fld.name, getattr(old, fld.name)) for fld in old._meta.fields if fld.name != old._meta.pk]);
  return self.__class__.objects.create(**new_kwargs)

There’s a clone snippet here, which you can add to your model which does this:

def clone(self):
  new_kwargs = dict([(fld.name, getattr(old, fld.name)) for fld in old._meta.fields if fld.name != old._meta.pk]);
  return self.__class__.objects.create(**new_kwargs)

回答 5

如何执行此操作已添加到Django1.4中的官方Django文档中

https://docs.djangoproject.com/en/1.10/topics/db/queries/#copying-model-instances

官方答案与miah的答案相似,但是文档指出了继承和相关对象方面的一些困难,因此您可能应该确保已阅读文档。

How to do this was added to the official Django docs in Django1.4

https://docs.djangoproject.com/en/1.10/topics/db/queries/#copying-model-instances

The official answer is similar to miah’s answer, but the docs point out some difficulties with inheritance and related objects, so you should probably make sure you read the docs.


回答 6

我遇到了一些公认的答案。这是我的解决方案。

import copy

def clone(instance):
    cloned = copy.copy(instance) # don't alter original instance
    cloned.pk = None
    try:
        delattr(cloned, '_prefetched_objects_cache')
    except AttributeError:
        pass
    return cloned

注意:这使用的解决方案未在Django文档中得到正式批准,并且在以后的版本中可能会停止使用。我在1.9.13中进行了测试。

第一个改进是,它允许您通过使用继续使用原始实例copy.copy。即使您不打算重用该实例,如果要克隆的实例作为参数传递给函数,执行此步骤也可能更安全。否则,函数返回时,调用方将意外地拥有其他实例。

copy.copy似乎以所需的方式生成了Django模型实例的浅表副本。这是我未发现的东西之一,但是它可以通过酸洗和酸洗来工作,因此可能得到了很好的支持。

其次,批准的答案将把任何预取结果附加到新实例上。除非您明确复制多对多关系,否则这些结果不应与新实例相关联。如果遍历预取的关系,将得到与数据库不匹配的结果。添加预取时破坏工作代码可能会令人讨厌。

删除_prefetched_objects_cache是一种剥离所有预取的快捷方法。随后的许多访问就像从未进行过预取一样工作。使用以下划线开头的未记录属性可能会引起兼容性问题,但现在可以使用。

I’ve run into a couple gotchas with the accepted answer. Here is my solution.

import copy

def clone(instance):
    cloned = copy.copy(instance) # don't alter original instance
    cloned.pk = None
    try:
        delattr(cloned, '_prefetched_objects_cache')
    except AttributeError:
        pass
    return cloned

Note: this uses solutions that aren’t officially sanctioned in the Django docs, and they may cease to work in future versions. I tested this in 1.9.13.

The first improvement is that it allows you to continue using the original instance, by using copy.copy. Even if you don’t intend to reuse the instance, it can be safer to do this step if the instance you’re cloning was passed as an argument to a function. If not, the caller will unexpectedly have a different instance when the function returns.

copy.copy seems to produce a shallow copy of a Django model instance in the desired way. This is one of the things I did not find documented, but it works by pickling and unpickling, so it’s probably well-supported.

Secondly, the approved answer will leave any prefetched results attached to the new instance. Those results shouldn’t be associated with the new instance, unless you explicitly copy the to-many relationships. If you traverse the the prefetched relationships, you will get results that don’t match the database. Breaking working code when you add a prefetch can be a nasty surprise.

Deleting _prefetched_objects_cache is a quick-and-dirty way to strip away all prefetches. Subsequent to-many accesses work as if there never was a prefetch. Using an undocumented property that begins with an underscore is probably asking for compatibility trouble, but it works for now.


回答 7

将pk设置为None更好,Sinse Django可以为您正确创建一个pk

object_copy = MyObject.objects.get(pk=...)
object_copy.pk = None
object_copy.save()

setting pk to None is better, sinse Django can correctly create a pk for you

object_copy = MyObject.objects.get(pk=...)
object_copy.pk = None
object_copy.save()

回答 8

这是克隆模型实例的另一种方法:

d = Foo.objects.filter(pk=1).values().first()   
d.update({'id': None})
duplicate = Foo.objects.create(**d)

This is yet another way of cloning the model instance:

d = Foo.objects.filter(pk=1).values().first()   
d.update({'id': None})
duplicate = Foo.objects.create(**d)

回答 9

要克隆具有多个继承级别(即> = 2或下面的ModelC)的模型

class ModelA(models.Model):
    info1 = models.CharField(max_length=64)

class ModelB(ModelA):
    info2 = models.CharField(max_length=64)

class ModelC(ModelB):
    info3 = models.CharField(max_length=64)

请在这里参考问题。

To clone a model with multiple inheritance levels, i.e. >= 2, or ModelC below

class ModelA(models.Model):
    info1 = models.CharField(max_length=64)

class ModelB(ModelA):
    info2 = models.CharField(max_length=64)

class ModelC(ModelB):
    info3 = models.CharField(max_length=64)

Please refer the question here.


回答 10

试试这个

original_object = Foo.objects.get(pk="foo")
v = vars(original_object)
v.pop("pk")
new_object = Foo(**v)
new_object.save()

Try this

original_object = Foo.objects.get(pk="foo")
v = vars(original_object)
v.pop("pk")
new_object = Foo(**v)
new_object.save()

如何在OSX 10.6中将MySQLdb与Python和Django一起使用?

问题:如何在OSX 10.6中将MySQLdb与Python和Django一起使用?

对于OSX 10.6用户,这是一个讨论很多的问题,但是我一直找不到能够解决问题的解决方案。这是我的设置:

Python 2.6.1 64位Django 1.2.1 MySQL 5.1.47 osx10.6 64位

我使用–no-site-packages创建了一个virtualenvwrapper,然后安装了Django。当我激活virtualenv并运行python manage.py syncdb时,出现以下错误:

Traceback (most recent call last):
File "manage.py", line 11, in <module>
  execute_manager(settings)
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/core/management/__init__.py", line 438, in execute_manager
  utility.execute()
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/core/management/__init__.py", line 379, in execute
  self.fetch_command(subcommand).run_from_argv(self.argv)
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/core/management/__init__.py", line 257, in fetch_command
  klass = load_command_class(app_name, subcommand)
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/core/management/__init__.py", line 67, in load_command_class
  module = import_module('%s.management.commands.%s' % (app_name, name))
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/utils/importlib.py", line 35, in import_module
  __import__(name)
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/core/management/commands/syncdb.py", line 7, in <module>
from django.core.management.sql import custom_sql_for_model, emit_post_sync_signal
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/core/management/sql.py", line 5, in <module>
from django.contrib.contenttypes import generic
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/contrib/contenttypes/generic.py", line 6, in <module>
  from django.db import connection
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/db/__init__.py", line 75, in <module>
  connection = connections[DEFAULT_DB_ALIAS]
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/db/utils.py", line 91, in __getitem__
  backend = load_backend(db['ENGINE'])
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/db/utils.py", line 32, in load_backend
  return import_module('.base', backend_name)
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/utils/importlib.py", line 35, in import_module
  __import__(name)
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/db/backends/mysql/base.py", line 14, in <module>
  raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e)
django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module: No module named MySQLdb

我还安装了MySQL for Python适配器,但无济于事(也许我安装不正确?)。

有人处理过吗?

This is a much discussed issue for OSX 10.6 users, but I haven’t been able to find a solution that works. Here’s my setup:

Python 2.6.1 64bit Django 1.2.1 MySQL 5.1.47 osx10.6 64bit

I create a virtualenvwrapper with –no-site-packages, then installed Django. When I activate the virtualenv and run python manage.py syncdb, I get this error:

Traceback (most recent call last):
File "manage.py", line 11, in <module>
  execute_manager(settings)
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/core/management/__init__.py", line 438, in execute_manager
  utility.execute()
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/core/management/__init__.py", line 379, in execute
  self.fetch_command(subcommand).run_from_argv(self.argv)
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/core/management/__init__.py", line 257, in fetch_command
  klass = load_command_class(app_name, subcommand)
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/core/management/__init__.py", line 67, in load_command_class
  module = import_module('%s.management.commands.%s' % (app_name, name))
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/utils/importlib.py", line 35, in import_module
  __import__(name)
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/core/management/commands/syncdb.py", line 7, in <module>
from django.core.management.sql import custom_sql_for_model, emit_post_sync_signal
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/core/management/sql.py", line 5, in <module>
from django.contrib.contenttypes import generic
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/contrib/contenttypes/generic.py", line 6, in <module>
  from django.db import connection
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/db/__init__.py", line 75, in <module>
  connection = connections[DEFAULT_DB_ALIAS]
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/db/utils.py", line 91, in __getitem__
  backend = load_backend(db['ENGINE'])
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/db/utils.py", line 32, in load_backend
  return import_module('.base', backend_name)
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/utils/importlib.py", line 35, in import_module
  __import__(name)
File "/Users/joerobinson/.virtualenvs/dj_tut/lib/python2.6/site-packages/django/db/backends/mysql/base.py", line 14, in <module>
  raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e)
django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module: No module named MySQLdb

I’ve also installed the MySQL for Python adapter, but to no avail (maybe I installed it improperly?).

Anyone dealt with this before?


回答 0

我有同样的错误,pip install MySQL-python并为我解决了。

备用安装:

  • 如果您没有点子,easy_install MySQL-python应该可以。
  • 如果您的python是由打包系统管理的,则可能必须使用该系统(例如sudo apt-get install ...

下面,Soli指出如果收到以下错误:

EnvironmentError: mysql_config not found

…然后您还有另一个系统依赖性问题。解决方案因系统而异,但对于Debian衍生的系统:

sudo apt-get install python-mysqldb

I had the same error and pip install MySQL-python solved it for me.

Alternate installs:

  • If you don’t have pip, easy_install MySQL-python should work.
  • If your python is managed by a packaging system, you might have to use that system (e.g. sudo apt-get install ...)

Below, Soli notes that if you receive the following error:

EnvironmentError: mysql_config not found

… then you have a further system dependency issue. Solving this will vary from system to system, but for Debian-derived systems:

sudo apt-get install python-mysqldb


回答 1

运行Ubuntu,我必须做:

sudo apt-get install python-mysqldb

Running Ubuntu, I had to do:

sudo apt-get install python-mysqldb

回答 2

除了其他答案,以下内容还帮助我完成了mysql-python的安装:

virtualenv,mysql-python,pip:有人知道吗?

在Ubuntu上…

apt-get install libmysqlclient-dev
apt-get install python-dev
pip install mysql-python

如果您没有适当的权限,请不要忘记在命令开头添加“ sudo”。

Adding to other answers, the following helped me finish the installation mysql-python:

virtualenv, mysql-python, pip: anyone know how?

On Ubuntu…

apt-get install libmysqlclient-dev
apt-get install python-dev
pip install mysql-python

Don’t forget to add ‘sudo’ to the beginning of commands if you don’t have the proper permissions.


回答 3

试试下面的命令。他们为我工作:

brew install mysql-connector-c 
pip install MySQL-python

Try this the commands below. They work for me:

brew install mysql-connector-c 
pip install MySQL-python

回答 4

mysql_config必须在路上。在Mac上,执行

export PATH=$PATH:/usr/local/mysql/bin/
pip install MySQL-python

mysql_config must be on the path. On Mac, do

export PATH=$PATH:/usr/local/mysql/bin/
pip install MySQL-python

回答 5

pip install mysql-python

提出了一个错误:

EnvironmentError:找不到mysql_config

sudo apt-get install python-mysqldb

解决了问题。

pip install mysql-python

raised an error:

EnvironmentError: mysql_config not found

sudo apt-get install python-mysqldb

fixed the problem.


回答 6

我如何工作的:

virtualenv -p python3.5 env/test

在采购我的环境后:

pip install pymysql
pip install django

然后,我运行了startproject并在manage.py中添加了以下内容:

+ try:
+     import pymysql
+     pymysql.install_as_MySQLdb()
+ except:
+     pass

此外,还更新了此内部设置:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'foobar_db',
        'USER': 'foobaruser',
        'PASSWORD': 'foobarpwd',
    }
}

我也已经configparser==3.5.0在我的virtualenv中安装了,不确定是否需要…

希望能帮助到你,

How I got it working:

virtualenv -p python3.5 env/test

After sourcing my env:

pip install pymysql
pip install django

Then, I ran the startproject and inside the manage.py, I added this:

+ try:
+     import pymysql
+     pymysql.install_as_MySQLdb()
+ except:
+     pass

Also, updated this inside settings:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'foobar_db',
        'USER': 'foobaruser',
        'PASSWORD': 'foobarpwd',
    }
}

I also have configparser==3.5.0 installed in my virtualenv, not sure if that was required or not…

Hope it helps,


回答 7

以下对我来说运行64位Ubuntu 13.10的完美工作:

sudo apt-get install libmysqlclient-dev
sudo apt-get install python-dev

现在,导航到您的virtualenv(例如env文件夹)并执行以下操作:

sudo ./bin/pip install mysql-python

实际上,我在另一个问题中找到了解决方案,并在下面引用了它:

如果使用–no-site-packages开关(默认设置)创建了virtualenv,则虚拟环境软件包中不包括系统范围内已安装的附加内容,例如MySQLdb。

您需要使用随virtualenv一起安装的pip命令安装MySQLdb。使用bin / activate脚本激活virtualenv,或者在virtualenv中使用bin / pip在本地安装MySQLdb库。

或者,使用–system-site-package开关创建包含系统站点包的新virtualenv。

我认为这也适用于OSX。唯一的问题是获得等效的安装命令libmysqlclient-devpython-dev因为mysql-python我猜它们是编译所必需的 。

希望这可以帮助。

The following worked perfectly for me, running Ubuntu 13.10 64-bit:

sudo apt-get install libmysqlclient-dev
sudo apt-get install python-dev

Now, navigate to your virtualenv (such as env folder) and execute the following:

sudo ./bin/pip install mysql-python

I actually found the solution in a separate question and I am quoting it below:

If you have created the virtualenv with the –no-site-packages switch (the default), then system-wide installed additions such as MySQLdb are not included in the virtual environment packages.

You need to install MySQLdb with the pip command installed with the virtualenv. Either activate the virtualenv with the bin/activate script, or use bin/pip from within the virtualenv to install the MySQLdb library locally as well.

Alternatively, create a new virtualenv with system site-packages included by using the –system-site-package switch.

I think this should also work with OSX. The only problem would be getting an equivalent command for installing libmysqlclient-dev and python-dev as they are needed to compile mysql-python I guess.

Hope this helps.


回答 8

试试这个:这为我解决了这个问题。

pip安装MySQL-python

Try this: This solved the issue for me .

pip install MySQL-python


回答 9

此问题是由于MySQL for Python适配器安装不完整/错误导致的。具体来说,我必须编辑mysql_config文件的路径以指向/ usr / local / mysql / bin / mysql_config-在本文中进行了详细讨论:http : //dakrauth.com/blog/entry/python-and- django-setup-mac-os-x-豹/

This issue was the result of an incomplete / incorrect installation of the MySQL for Python adapter. Specifically, I had to edit the path to the mysql_config file to point to /usr/local/mysql/bin/mysql_config – discussed in greater detail in this article: http://dakrauth.com/blog/entry/python-and-django-setup-mac-os-x-leopard/


回答 10

sudo apt-get install python-mysqldb 在ubuntu中完美工作

pip install mysql-python引发环境错误

sudo apt-get install python-mysqldb works perfectly in ubuntu

pip install mysql-python raises an Environment Error


回答 11

这适用于Red Hat Enterprise Linux Server 6.4版

sudo yum install mysql-devel
sudo yum install python-devel
pip install mysql-python

This worked for Red Hat Enterprise Linux Server release 6.4

sudo yum install mysql-devel
sudo yum install python-devel
pip install mysql-python

回答 12

您可以安装为 pip install mysqlclient

You can install as pip install mysqlclient


回答 13

我进行了OSX Mavericks和Pycharm 3的升级,并开始出现此错误,我使用了pip并易于安装,并得到了以下错误:

命令’/ usr / bin / clang’失败,退出状态为1。

所以我需要更新到Xcode 5,然后再次尝试使用pip进行安装。

pip install mysql-python

那解决了所有问题。

I made the upgrade to OSX Mavericks and Pycharm 3 and start to get this error, i used pip and easy install and got the error:

command’/usr/bin/clang’ failed with exit status 1.

So i need to update to Xcode 5 and tried again to install using pip.

pip install mysql-python

That fix all the problems.


回答 14

此处引发的错误是在导入python模块中。可以通过将python site-packages文件夹添加到OS X上的环境变量$ PYTHONPATH来解决。因此,我们可以将以下命令添加到.bash_profile文件中:

export PYTHONPATH="$PYTHONPATH:/usr/local/lib/pythonx.x/site-packages/"

*用您正在使用的python版本替换xx

The error raised here is in importing the python module. This can be solved by adding the python site-packages folder to the environment variable $PYTHONPATH on OS X. So we can add the following command to the .bash_profile file:

export PYTHONPATH="$PYTHONPATH:/usr/local/lib/pythonx.x/site-packages/"

*replace x.x with the python version you are using


回答 15

如果您使用的是python3,请尝试以下操作(我的操作系统是Ubuntu 16.04):

sudo apt-get install python3-mysqldb

If you are using python3, then try this(My OS is Ubuntu 16.04):

sudo apt-get install python3-mysqldb

回答 16

pip在Windows 8 64位系统上对我不起作用。 easy_install mysql-python为我工作。easy_install如果pip不起作用,您可以用来避免在Windows上生成二进制文件。

pip did not work for me on windows 8 64 bits system. easy_install mysql-python works for me. You can use easy_install to avoid building binaries on windows if pip does not work.


回答 17

我在OSX 10.6.6上遇到了相同的问题。但是,只是一个简单easy_install mysql-python的终端无法解决它,因为随之而来的另一个麻烦是:

error: command 'gcc-4.2' failed with exit status 1

显然,从XCode3(OSX 10.6附带提供)升级到XCode4后,就会出现此问题。此较新版本删除了对构建ppc拱的支持。如果相同,请尝试以下操作easy_install mysql-python

sudo bash
export ARCHFLAGS='-arch i386 -arch x86_64'
rm -r build
python setup.py build
python setup.py install

非常感谢Ned Deily的解决方案。在这里检查

I had the same problem on OSX 10.6.6. But just a simple easy_install mysql-python on terminal did not solve it as another hiccup followed:

error: command 'gcc-4.2' failed with exit status 1.

Apparently, this issue arises after upgrading from XCode3 (which is natively shipped with OSX 10.6) to XCode4. This newer ver removes support for building ppc arch. If its the same case, try doing as follows before easy_install mysql-python

sudo bash
export ARCHFLAGS='-arch i386 -arch x86_64'
rm -r build
python setup.py build
python setup.py install

Many thanks to Ned Deily for this solution. Check here


回答 18

对我来说,只需重新安装mysql-python即可解决问题

pip uninstall mysql-python
pip install mysql-python

For me the problem got solved by simply reinstalling mysql-python

pip uninstall mysql-python
pip install mysql-python

回答 19

安装命令行工具对我有用:

xcode-select --install

Install Command Line Tools Works for me:

xcode-select --install

回答 20

我通过MySQL-python使用pip安装库克服了相同的问题。当我第一次在settings.py中更改数据库设置并执行makemigrations命令时,可以看到控制台上显示的消息(解决方案遵循以下消息,请看一下)。

  (vir_env) admins-MacBook-Pro-3:src admin$ python manage.py makemigrations
Traceback (most recent call last):
  File "manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/core/management/__init__.py", line 338, in execute_from_command_line
    utility.execute()
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/core/management/__init__.py", line 312, in execute
    django.setup()
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/__init__.py", line 18, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/apps/registry.py", line 108, in populate
    app_config.import_models(all_models)
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/apps/config.py", line 198, in import_models
    self.models_module = import_module(models_module_name)
  File "/usr/local/Cellar/python/2.7.12_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/importlib/__init__.py", line 37, in import_module
    __import__(name)
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/contrib/auth/models.py", line 41, in <module>
    class Permission(models.Model):
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/db/models/base.py", line 139, in __new__
    new_class.add_to_class('_meta', Options(meta, **kwargs))
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/db/models/base.py", line 324, in add_to_class
    value.contribute_to_class(cls, name)
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/db/models/options.py", line 250, in contribute_to_class
    self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/db/__init__.py", line 36, in __getattr__
    return getattr(connections[DEFAULT_DB_ALIAS], item)
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/db/utils.py", line 240, in __getitem__
    backend = load_backend(db['ENGINE'])
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/db/utils.py", line 111, in load_backend
    return import_module('%s.base' % backend_name)
  File "/usr/local/Cellar/python/2.7.12_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/importlib/__init__.py", line 37, in import_module
    __import__(name)
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/db/backends/mysql/base.py", line 27, in <module>
    raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e)
django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module: No module named MySQLdb

最后,我克服了以下问题:

(vir_env) admins-MacBook-Pro-3:src admin$ pip install MySQLdb
Collecting MySQLdb
  Could not find a version that satisfies the requirement MySQLdb (from versions: )
No matching distribution found for MySQLdb
(vir_env) admins-MacBook-Pro-3:src admin$ pip install MySQL-python
Collecting MySQL-python
  Downloading MySQL-python-1.2.5.zip (108kB)
    100% |████████████████████████████████| 112kB 364kB/s 
Building wheels for collected packages: MySQL-python
  Running setup.py bdist_wheel for MySQL-python ... done
  Stored in directory: /Users/admin/Library/Caches/pip/wheels/38/a3/89/ec87e092cfb38450fc91a62562055231deb0049a029054dc62
Successfully built MySQL-python
Installing collected packages: MySQL-python
Successfully installed MySQL-python-1.2.5
(vir_env) admins-MacBook-Pro-3:src admin$ python manage.py makemigrations
No changes detected
(vir_env) admins-MacBook-Pro-3:src admin$ python manage.py migrate
Operations to perform:
  Synchronize unmigrated apps: staticfiles, rest_framework, messages, crispy_forms
  Apply all migrations: admin, contenttypes, sessions, auth, PyApp
Synchronizing apps without migrations:
  Creating tables...
    Running deferred SQL...
  Installing custom SQL...
Running migrations:
  Rendering model states... DONE
  Applying PyApp.0001_initial... OK
  Applying PyApp.0002_auto_20170310_0936... OK
  Applying PyApp.0003_auto_20170310_0953... OK
  Applying PyApp.0004_auto_20170310_0954... OK
  Applying PyApp.0005_auto_20170311_0619... OK
  Applying PyApp.0006_auto_20170311_0622... OK
  Applying PyApp.0007_loraevksensor... OK
  Applying PyApp.0008_auto_20170315_0752... OK
  Applying PyApp.0009_auto_20170315_0753... OK
  Applying PyApp.0010_auto_20170315_0806... OK
  Applying PyApp.0011_auto_20170315_0814... OK
  Applying PyApp.0012_auto_20170315_0820... OK
  Applying PyApp.0013_auto_20170315_0822... OK
  Applying PyApp.0014_auto_20170315_0907... OK
  Applying PyApp.0015_auto_20170315_1041... OK
  Applying PyApp.0016_auto_20170315_1355... OK
  Applying PyApp.0017_auto_20170315_1401... OK
  Applying PyApp.0018_auto_20170331_1348... OK
  Applying PyApp.0019_auto_20170331_1349... OK
  Applying PyApp.0020_auto_20170331_1350... OK
  Applying PyApp.0021_auto_20170331_1458... OK
  Applying PyApp.0022_delete_postoffice... OK
  Applying PyApp.0023_posoffice... OK
  Applying PyApp.0024_auto_20170331_1504... OK
  Applying PyApp.0025_auto_20170331_1511... OK
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying sessions.0001_initial... OK
(vir_env) admins-MacBook-Pro-3:src admin$ 

I overcame the same problem by installing MySQL-python library using pip. You can see the message displayed on my console when I first changed my database settings in settings.py and executed makemigrations command(The solution is following the below message, just see that).

  (vir_env) admins-MacBook-Pro-3:src admin$ python manage.py makemigrations
Traceback (most recent call last):
  File "manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/core/management/__init__.py", line 338, in execute_from_command_line
    utility.execute()
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/core/management/__init__.py", line 312, in execute
    django.setup()
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/__init__.py", line 18, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/apps/registry.py", line 108, in populate
    app_config.import_models(all_models)
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/apps/config.py", line 198, in import_models
    self.models_module = import_module(models_module_name)
  File "/usr/local/Cellar/python/2.7.12_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/importlib/__init__.py", line 37, in import_module
    __import__(name)
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/contrib/auth/models.py", line 41, in <module>
    class Permission(models.Model):
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/db/models/base.py", line 139, in __new__
    new_class.add_to_class('_meta', Options(meta, **kwargs))
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/db/models/base.py", line 324, in add_to_class
    value.contribute_to_class(cls, name)
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/db/models/options.py", line 250, in contribute_to_class
    self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/db/__init__.py", line 36, in __getattr__
    return getattr(connections[DEFAULT_DB_ALIAS], item)
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/db/utils.py", line 240, in __getitem__
    backend = load_backend(db['ENGINE'])
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/db/utils.py", line 111, in load_backend
    return import_module('%s.base' % backend_name)
  File "/usr/local/Cellar/python/2.7.12_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/importlib/__init__.py", line 37, in import_module
    __import__(name)
  File "/Users/admin/Desktop/SetUp1/vir_env/lib/python2.7/site-packages/django/db/backends/mysql/base.py", line 27, in <module>
    raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e)
django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module: No module named MySQLdb

Finally I overcame this problem as follows:

(vir_env) admins-MacBook-Pro-3:src admin$ pip install MySQLdb
Collecting MySQLdb
  Could not find a version that satisfies the requirement MySQLdb (from versions: )
No matching distribution found for MySQLdb
(vir_env) admins-MacBook-Pro-3:src admin$ pip install MySQL-python
Collecting MySQL-python
  Downloading MySQL-python-1.2.5.zip (108kB)
    100% |████████████████████████████████| 112kB 364kB/s 
Building wheels for collected packages: MySQL-python
  Running setup.py bdist_wheel for MySQL-python ... done
  Stored in directory: /Users/admin/Library/Caches/pip/wheels/38/a3/89/ec87e092cfb38450fc91a62562055231deb0049a029054dc62
Successfully built MySQL-python
Installing collected packages: MySQL-python
Successfully installed MySQL-python-1.2.5
(vir_env) admins-MacBook-Pro-3:src admin$ python manage.py makemigrations
No changes detected
(vir_env) admins-MacBook-Pro-3:src admin$ python manage.py migrate
Operations to perform:
  Synchronize unmigrated apps: staticfiles, rest_framework, messages, crispy_forms
  Apply all migrations: admin, contenttypes, sessions, auth, PyApp
Synchronizing apps without migrations:
  Creating tables...
    Running deferred SQL...
  Installing custom SQL...
Running migrations:
  Rendering model states... DONE
  Applying PyApp.0001_initial... OK
  Applying PyApp.0002_auto_20170310_0936... OK
  Applying PyApp.0003_auto_20170310_0953... OK
  Applying PyApp.0004_auto_20170310_0954... OK
  Applying PyApp.0005_auto_20170311_0619... OK
  Applying PyApp.0006_auto_20170311_0622... OK
  Applying PyApp.0007_loraevksensor... OK
  Applying PyApp.0008_auto_20170315_0752... OK
  Applying PyApp.0009_auto_20170315_0753... OK
  Applying PyApp.0010_auto_20170315_0806... OK
  Applying PyApp.0011_auto_20170315_0814... OK
  Applying PyApp.0012_auto_20170315_0820... OK
  Applying PyApp.0013_auto_20170315_0822... OK
  Applying PyApp.0014_auto_20170315_0907... OK
  Applying PyApp.0015_auto_20170315_1041... OK
  Applying PyApp.0016_auto_20170315_1355... OK
  Applying PyApp.0017_auto_20170315_1401... OK
  Applying PyApp.0018_auto_20170331_1348... OK
  Applying PyApp.0019_auto_20170331_1349... OK
  Applying PyApp.0020_auto_20170331_1350... OK
  Applying PyApp.0021_auto_20170331_1458... OK
  Applying PyApp.0022_delete_postoffice... OK
  Applying PyApp.0023_posoffice... OK
  Applying PyApp.0024_auto_20170331_1504... OK
  Applying PyApp.0025_auto_20170331_1511... OK
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying sessions.0001_initial... OK
(vir_env) admins-MacBook-Pro-3:src admin$ 

回答 21

运行此命令

sudo pip install mysql-python;

现在您可以运行命令了。

python manage.py startapp filename;

Run this command

sudo pip install mysql-python;

now you can run your command.

python manage.py startapp filename;

回答 22

我遇到了类似的情况,例如您在Mac OS X上的virtualenv中使用python3.7和django 2.1。尝试运行命令:

pip install mysql-python
pip install pymysql

__init__.py在您的项目文件夹中编辑文件,并添加以下内容:

import pymysql

pymysql.install_as_MySQLdb()

然后运行:python3 manage.py runserverpython manage.py runserver

I encountered similar situations like yours that I am using python3.7 and django 2.1 in virtualenv on mac osx. Try to run command:

pip install mysql-python
pip install pymysql

And edit __init__.py file in your project folder and add following:

import pymysql

pymysql.install_as_MySQLdb()

Then run: python3 manage.py runserver or python manage.py runserver


将Django模型对象转换为所有字段均完整的dict

问题:将Django模型对象转换为所有字段均完整的dict

如何将Django模型对象转换为具有所有字段的字典?理想情况下,所有内容都包含带有的外键和字段editable=False

让我详细说明。假设我有一个类似以下的Django模型:

from django.db import models

class OtherModel(models.Model): pass

class SomeModel(models.Model):
    normal_value = models.IntegerField()
    readonly_value = models.IntegerField(editable=False)
    auto_now_add = models.DateTimeField(auto_now_add=True)
    foreign_key = models.ForeignKey(OtherModel, related_name="ref1")
    many_to_many = models.ManyToManyField(OtherModel, related_name="ref2")

在终端中,我已执行以下操作:

other_model = OtherModel()
other_model.save()
instance = SomeModel()
instance.normal_value = 1
instance.readonly_value = 2
instance.foreign_key = other_model
instance.save()
instance.many_to_many.add(other_model)
instance.save()

我想将其转换为以下字典:

{'auto_now_add': datetime.datetime(2015, 3, 16, 21, 34, 14, 926738, tzinfo=<UTC>),
 'foreign_key': 1,
 'id': 1,
 'many_to_many': [1],
 'normal_value': 1,
 'readonly_value': 2}

答案不令人满意的问题:

Django:将整个模型对象集转换为单个字典

如何将Django模型对象转换为字典并仍然具有其外键?

How does one convert a Django Model object to a dict with all of its fields? All ideally includes foreign keys and fields with editable=False.

Let me elaborate. Let’s say I have a Django model like the following:

from django.db import models

class OtherModel(models.Model): pass

class SomeModel(models.Model):
    normal_value = models.IntegerField()
    readonly_value = models.IntegerField(editable=False)
    auto_now_add = models.DateTimeField(auto_now_add=True)
    foreign_key = models.ForeignKey(OtherModel, related_name="ref1")
    many_to_many = models.ManyToManyField(OtherModel, related_name="ref2")

In the terminal, I have done the following:

other_model = OtherModel()
other_model.save()
instance = SomeModel()
instance.normal_value = 1
instance.readonly_value = 2
instance.foreign_key = other_model
instance.save()
instance.many_to_many.add(other_model)
instance.save()

I want to convert this to the following dictionary:

{'auto_now_add': datetime.datetime(2015, 3, 16, 21, 34, 14, 926738, tzinfo=<UTC>),
 'foreign_key': 1,
 'id': 1,
 'many_to_many': [1],
 'normal_value': 1,
 'readonly_value': 2}

Questions with unsatisfactory answers:

Django: Converting an entire set of a Model’s objects into a single dictionary

How can I turn Django Model objects into a dictionary and still have their foreign keys?


回答 0

有多种方法可将实例转换为字典,并具有不同程度的特殊情况处理和接近所需结果的程度。


1。 instance.__dict__

instance.__dict__

哪个返回

{'_foreign_key_cache': <OtherModel: OtherModel object>,
 '_state': <django.db.models.base.ModelState at 0x7ff0993f6908>,
 'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=<UTC>),
 'foreign_key_id': 2,
 'id': 1,
 'normal_value': 1,
 'readonly_value': 2}

到目前为止,这是最简单的方法,但是缺少many_to_manyforeign_key被错误命名,并且其中有两个多余的多余内容。


2。 model_to_dict

from django.forms.models import model_to_dict
model_to_dict(instance)

哪个返回

{'foreign_key': 2,
 'id': 1,
 'many_to_many': [<OtherModel: OtherModel object>],
 'normal_value': 1}

这是唯一的many_to_many,但缺少不可编辑的字段。


3。 model_to_dict(..., fields=...)

from django.forms.models import model_to_dict
model_to_dict(instance, fields=[field.name for field in instance._meta.fields])

哪个返回

{'foreign_key': 2, 'id': 1, 'normal_value': 1}

这绝对比标准model_to_dict调用差。


4。 query_set.values()

SomeModel.objects.filter(id=instance.id).values()[0]

哪个返回

{'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=<UTC>),
 'foreign_key_id': 2,
 'id': 1,
 'normal_value': 1,
 'readonly_value': 2}

这与输出相同,instance.__dict__但没有额外的字段。 foreign_key_id仍然是错误的,many_to_many仍然不见了。


5.自定义功能

django的代码model_to_dict具有大部分答案。它显式删除了不可编辑的字段,因此删除该检查并获取多对多字段的外键ID会导致以下代码按预期运行:

from itertools import chain

def to_dict(instance):
    opts = instance._meta
    data = {}
    for f in chain(opts.concrete_fields, opts.private_fields):
        data[f.name] = f.value_from_object(instance)
    for f in opts.many_to_many:
        data[f.name] = [i.id for i in f.value_from_object(instance)]
    return data

虽然这是最复杂的选项,但调用to_dict(instance)会给我们确切的预期结果:

{'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=<UTC>),
 'foreign_key': 2,
 'id': 1,
 'many_to_many': [2],
 'normal_value': 1,
 'readonly_value': 2}

6.使用序列化器

Django Rest Framework的ModelSerialzer允许您从模型自动构建序列化器。

from rest_framework import serializers
class SomeModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = SomeModel
        fields = "__all__"

SomeModelSerializer(instance).data

退货

{'auto_now_add': '2018-12-20T21:34:29.494827Z',
 'foreign_key': 2,
 'id': 1,
 'many_to_many': [2],
 'normal_value': 1,
 'readonly_value': 2}

这几乎与自定义函数一样好,但是auto_now_add是字符串而不是datetime对象。


奖金回合:更好的模型印刷

如果您想要一个具有更好的python命令行显示的Django模型,请让您的模型将以下子类:

from django.db import models
from itertools import chain

class PrintableModel(models.Model):
    def __repr__(self):
        return str(self.to_dict())

    def to_dict(instance):
        opts = instance._meta
        data = {}
        for f in chain(opts.concrete_fields, opts.private_fields):
            data[f.name] = f.value_from_object(instance)
        for f in opts.many_to_many:
            data[f.name] = [i.id for i in f.value_from_object(instance)]
        return data

    class Meta:
        abstract = True

因此,例如,如果我们这样定义模型:

class OtherModel(PrintableModel): pass

class SomeModel(PrintableModel):
    normal_value = models.IntegerField()
    readonly_value = models.IntegerField(editable=False)
    auto_now_add = models.DateTimeField(auto_now_add=True)
    foreign_key = models.ForeignKey(OtherModel, related_name="ref1")
    many_to_many = models.ManyToManyField(OtherModel, related_name="ref2")

SomeModel.objects.first()现在调用将产生如下输出:

{'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=<UTC>),
 'foreign_key': 2,
 'id': 1,
 'many_to_many': [2],
 'normal_value': 1,
 'readonly_value': 2}

There are many ways to convert an instance to a dictionary, with varying degrees of corner case handling and closeness to the desired result.


1. instance.__dict__

instance.__dict__

which returns

{'_foreign_key_cache': <OtherModel: OtherModel object>,
 '_state': <django.db.models.base.ModelState at 0x7ff0993f6908>,
 'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=<UTC>),
 'foreign_key_id': 2,
 'id': 1,
 'normal_value': 1,
 'readonly_value': 2}

This is by far the simplest, but is missing many_to_many, foreign_key is misnamed, and it has two unwanted extra things in it.


2. model_to_dict

from django.forms.models import model_to_dict
model_to_dict(instance)

which returns

{'foreign_key': 2,
 'id': 1,
 'many_to_many': [<OtherModel: OtherModel object>],
 'normal_value': 1}

This is the only one with many_to_many, but is missing the uneditable fields.


3. model_to_dict(..., fields=...)

from django.forms.models import model_to_dict
model_to_dict(instance, fields=[field.name for field in instance._meta.fields])

which returns

{'foreign_key': 2, 'id': 1, 'normal_value': 1}

This is strictly worse than the standard model_to_dict invocation.


4. query_set.values()

SomeModel.objects.filter(id=instance.id).values()[0]

which returns

{'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=<UTC>),
 'foreign_key_id': 2,
 'id': 1,
 'normal_value': 1,
 'readonly_value': 2}

This is the same output as instance.__dict__ but without the extra fields. foreign_key_id is still wrong and many_to_many is still missing.


5. Custom Function

The code for django’s model_to_dict had most of the answer. It explicitly removed non-editable fields, so removing that check and getting the ids of foreign keys for many to many fields results in the following code which behaves as desired:

from itertools import chain

def to_dict(instance):
    opts = instance._meta
    data = {}
    for f in chain(opts.concrete_fields, opts.private_fields):
        data[f.name] = f.value_from_object(instance)
    for f in opts.many_to_many:
        data[f.name] = [i.id for i in f.value_from_object(instance)]
    return data

While this is the most complicated option, calling to_dict(instance) gives us exactly the desired result:

{'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=<UTC>),
 'foreign_key': 2,
 'id': 1,
 'many_to_many': [2],
 'normal_value': 1,
 'readonly_value': 2}

6. Use Serializers

Django Rest Framework‘s ModelSerialzer allows you to build a serializer automatically from a model.

from rest_framework import serializers
class SomeModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = SomeModel
        fields = "__all__"

SomeModelSerializer(instance).data

returns

{'auto_now_add': '2018-12-20T21:34:29.494827Z',
 'foreign_key': 2,
 'id': 1,
 'many_to_many': [2],
 'normal_value': 1,
 'readonly_value': 2}

This is almost as good as the custom function, but auto_now_add is a string instead of a datetime object.


Bonus Round: better model printing

If you want a django model that has a better python command-line display, have your models child-class the following:

from django.db import models
from itertools import chain

class PrintableModel(models.Model):
    def __repr__(self):
        return str(self.to_dict())

    def to_dict(instance):
        opts = instance._meta
        data = {}
        for f in chain(opts.concrete_fields, opts.private_fields):
            data[f.name] = f.value_from_object(instance)
        for f in opts.many_to_many:
            data[f.name] = [i.id for i in f.value_from_object(instance)]
        return data

    class Meta:
        abstract = True

So, for example, if we define our models as such:

class OtherModel(PrintableModel): pass

class SomeModel(PrintableModel):
    normal_value = models.IntegerField()
    readonly_value = models.IntegerField(editable=False)
    auto_now_add = models.DateTimeField(auto_now_add=True)
    foreign_key = models.ForeignKey(OtherModel, related_name="ref1")
    many_to_many = models.ManyToManyField(OtherModel, related_name="ref2")

Calling SomeModel.objects.first() now gives output like this:

{'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=<UTC>),
 'foreign_key': 2,
 'id': 1,
 'many_to_many': [2],
 'normal_value': 1,
 'readonly_value': 2}

回答 1

我找到了一个整洁的解决方案以得到结果:

假设您有一个模型对象o

只需调用:

type(o).objects.filter(pk=o.pk).values().first()

I found a neat solution to get to result:

Suppose you have an model object o:

Just call:

type(o).objects.filter(pk=o.pk).values().first()

回答 2

@Zags解决方案很棒!

不过,我将为datefields添加一个条件,以使其对JSON友好。

奖金回合

如果您希望Django模型具有更好的python命令行显示,请让您的模型子类具有以下功能:

from django.db import models
from django.db.models.fields.related import ManyToManyField

class PrintableModel(models.Model):
    def __repr__(self):
        return str(self.to_dict())

    def to_dict(self):
        opts = self._meta
        data = {}
        for f in opts.concrete_fields + opts.many_to_many:
            if isinstance(f, ManyToManyField):
                if self.pk is None:
                    data[f.name] = []
                else:
                    data[f.name] = list(f.value_from_object(self).values_list('pk', flat=True))
            elif isinstance(f, DateTimeField):
                if f.value_from_object(self) is not None:
                    data[f.name] = f.value_from_object(self).timestamp()
            else:
                data[f.name] = None
            else:
                data[f.name] = f.value_from_object(self)
        return data

    class Meta:
        abstract = True

因此,例如,如果我们这样定义模型:

class OtherModel(PrintableModel): pass

class SomeModel(PrintableModel):
    value = models.IntegerField()
    value2 = models.IntegerField(editable=False)
    created = models.DateTimeField(auto_now_add=True)
    reference1 = models.ForeignKey(OtherModel, related_name="ref1")
    reference2 = models.ManyToManyField(OtherModel, related_name="ref2")

SomeModel.objects.first()现在调用将产生如下输出:

{'created': 1426552454.926738,
'value': 1, 'value2': 2, 'reference1': 1, u'id': 1, 'reference2': [1]}

@Zags solution was gorgeous!

I would add, though, a condition for datefields in order to make it JSON friendly.

Bonus Round

If you want a django model that has a better python command-line display, have your models child class the following:

from django.db import models
from django.db.models.fields.related import ManyToManyField

class PrintableModel(models.Model):
    def __repr__(self):
        return str(self.to_dict())

    def to_dict(self):
        opts = self._meta
        data = {}
        for f in opts.concrete_fields + opts.many_to_many:
            if isinstance(f, ManyToManyField):
                if self.pk is None:
                    data[f.name] = []
                else:
                    data[f.name] = list(f.value_from_object(self).values_list('pk', flat=True))
            elif isinstance(f, DateTimeField):
                if f.value_from_object(self) is not None:
                    data[f.name] = f.value_from_object(self).timestamp()
            else:
                data[f.name] = None
            else:
                data[f.name] = f.value_from_object(self)
        return data

    class Meta:
        abstract = True

So, for example, if we define our models as such:

class OtherModel(PrintableModel): pass

class SomeModel(PrintableModel):
    value = models.IntegerField()
    value2 = models.IntegerField(editable=False)
    created = models.DateTimeField(auto_now_add=True)
    reference1 = models.ForeignKey(OtherModel, related_name="ref1")
    reference2 = models.ManyToManyField(OtherModel, related_name="ref2")

Calling SomeModel.objects.first() now gives output like this:

{'created': 1426552454.926738,
'value': 1, 'value2': 2, 'reference1': 1, u'id': 1, 'reference2': [1]}

回答 3

最简单的方法

  1. 如果您的查询是Model.Objects.get():

    get()将返回单个实例,因此您可以直接__dict__从实例中使用

    model_dict = Model.Objects.get().__dict__

  2. 对于filter()/ all():

    all()/ filter()将返回实例列表,因此您可以values()用来获取对象列表。

    model_values = Model.Objects.all()。values()

Simplest way,

  1. If your query is Model.Objects.get():

    get() will return single instance so you can direct use __dict__ from your instance

    model_dict = Model.Objects.get().__dict__

  2. for filter()/all():

    all()/filter() will return list of instances so you can use values() to get list of objects.

    model_values = Model.Objects.all().values()


回答 4

只是vars(obj),它将说明对象的整个值

>>> obj_attrs = vars(obj)
>>> obj_attrs
 {'_file_data_cache': <FileData: Data>,
  '_state': <django.db.models.base.ModelState at 0x7f5c6733bad0>,
  'aggregator_id': 24,
  'amount': 5.0,
  'biller_id': 23,
  'datetime': datetime.datetime(2018, 1, 31, 18, 43, 58, 933277, tzinfo=<UTC>),
  'file_data_id': 797719,
 }

您也可以添加

>>> keys = obj_attrs.keys()
>>> temp = [obj_attrs.pop(key) if key.startswith('_') else None for key in keys]
>>> del temp
>>> obj_attrs
   {
    'aggregator_id': 24,
    'amount': 5.0,
    'biller_id': 23,
    'datetime': datetime.datetime(2018, 1, 31, 18, 43, 58, 933277, tzinfo=<UTC>),
    'file_data_id': 797719,
   }

just vars(obj) , it will state the whole values of the object

>>> obj_attrs = vars(obj)
>>> obj_attrs
 {'_file_data_cache': <FileData: Data>,
  '_state': <django.db.models.base.ModelState at 0x7f5c6733bad0>,
  'aggregator_id': 24,
  'amount': 5.0,
  'biller_id': 23,
  'datetime': datetime.datetime(2018, 1, 31, 18, 43, 58, 933277, tzinfo=<UTC>),
  'file_data_id': 797719,
 }

You can add this also

>>> keys = obj_attrs.keys()
>>> temp = [obj_attrs.pop(key) if key.startswith('_') else None for key in keys]
>>> del temp
>>> obj_attrs
   {
    'aggregator_id': 24,
    'amount': 5.0,
    'biller_id': 23,
    'datetime': datetime.datetime(2018, 1, 31, 18, 43, 58, 933277, tzinfo=<UTC>),
    'file_data_id': 797719,
   }

回答 5

更新资料

@zags发布的较新的汇总答案比我自己的答案更完整,更优雅。请改为参考该答案。

原版的

如果您愿意像@karthiker建议的那样定义自己的to_dict方法,那么就可以将此问题归结为集合问题。

>>># Returns a set of all keys excluding editable = False keys
>>>dict = model_to_dict(instance)
>>>dict

{u'id': 1L, 'reference1': 1L, 'reference2': [1L], 'value': 1}


>>># Returns a set of editable = False keys, misnamed foreign keys, and normal keys
>>>otherDict = SomeModel.objects.filter(id=instance.id).values()[0]
>>>otherDict

{'created': datetime.datetime(2014, 2, 21, 4, 38, 51, tzinfo=<UTC>),
 u'id': 1L,
 'reference1_id': 1L,
 'value': 1L,
 'value2': 2L}

我们需要从otherDict中删除贴标签的外键。

为此,我们可以使用一个循环来创建一个新字典,该字典除了包含下划线的项外,还包含所有项。或者,为了节省时间,我们可以将它们添加到原始字典中,因为字典只是在幕后设置的。

>>>for item in otherDict.items():
...    if "_" not in item[0]:
...            dict.update({item[0]:item[1]})
...
>>>

因此,我们只能用下面的字典

>>>dict
{'created': datetime.datetime(2014, 2, 21, 4, 38, 51, tzinfo=<UTC>),
 u'id': 1L,
 'reference1': 1L,
 'reference2': [1L],
 'value': 1,
 'value2': 2L}

然后您将其退回。

不利的一面是,您不能在editable = false字段名称中使用下划线。从好的方面来说,这将适用于用户创建的字段不包含下划线的任何字段集。

这不是执行此操作的最佳方法,但是在找到更直接的方法之前,它可以作为临时解决方案。

对于以下示例,将基于model_to_dict形成dict,并通过filter的values方法形成otherDict。我本来可以用模型自己完成的,但是我无法让我的机器接受otherModel。

>>> import datetime
>>> dict = {u'id': 1, 'reference1': 1, 'reference2': [1], 'value': 1}
>>> otherDict = {'created': datetime.datetime(2014, 2, 21, 4, 38, 51), u'id': 1, 'reference1_id': 1, 'value': 1, 'value2': 2}
>>> for item in otherDict.items():
...     if "_" not in item[0]:
...             dict.update({item[0]:item[1]})
...
>>> dict
{'reference1': 1, 'created': datetime.datetime(2014, 2, 21, 4, 38, 51), 'value2': 2, 'value': 1, 'id': 1, 'reference2': [1]}
>>>

我希望,这应该使您对问题的答案有个大概的了解。

Update

The newer aggregated answer posted by @zags is more complete and elegant than my own. Please refer to that answer instead.

Original

If you are willing to define your own to_dict method like @karthiker suggested, then that just boils this problem down to a sets problem.

>>># Returns a set of all keys excluding editable = False keys
>>>dict = model_to_dict(instance)
>>>dict

{u'id': 1L, 'reference1': 1L, 'reference2': [1L], 'value': 1}


>>># Returns a set of editable = False keys, misnamed foreign keys, and normal keys
>>>otherDict = SomeModel.objects.filter(id=instance.id).values()[0]
>>>otherDict

{'created': datetime.datetime(2014, 2, 21, 4, 38, 51, tzinfo=<UTC>),
 u'id': 1L,
 'reference1_id': 1L,
 'value': 1L,
 'value2': 2L}

We need to remove the mislabeled foreign keys from otherDict.

To do this, we can use a loop that makes a new dictionary that has every item except those with underscores in them. Or, to save time, we can just add those to the original dict since dictionaries are just sets under the hood.

>>>for item in otherDict.items():
...    if "_" not in item[0]:
...            dict.update({item[0]:item[1]})
...
>>>

Thus we are left with the following dict:

>>>dict
{'created': datetime.datetime(2014, 2, 21, 4, 38, 51, tzinfo=<UTC>),
 u'id': 1L,
 'reference1': 1L,
 'reference2': [1L],
 'value': 1,
 'value2': 2L}

And you just return that.

On the downside, you can’t use underscores in your editable=false field names. On the upside, this will work for any set of fields where the user-made fields do not contain underscores.

This is not the best way of doing this, but it could work as a temporary solution until a more direct method is found.

For the example below, dict would be formed based on model_to_dict and otherDict would be formed by filter’s values method. I would have done this with the models themselves, but I can’t get my machine to accept otherModel.

>>> import datetime
>>> dict = {u'id': 1, 'reference1': 1, 'reference2': [1], 'value': 1}
>>> otherDict = {'created': datetime.datetime(2014, 2, 21, 4, 38, 51), u'id': 1, 'reference1_id': 1, 'value': 1, 'value2': 2}
>>> for item in otherDict.items():
...     if "_" not in item[0]:
...             dict.update({item[0]:item[1]})
...
>>> dict
{'reference1': 1, 'created': datetime.datetime(2014, 2, 21, 4, 38, 51), 'value2': 2, 'value': 1, 'id': 1, 'reference2': [1]}
>>>

That should put you in a rough ballpark of the answer to your question, I hope.


回答 6

这里有很多有趣的解决方案。我的解决方案是使用dict理解将as_dict方法添加到模型中。

def as_dict(self):
    return dict((f.name, getattr(self, f.name)) for f in self._meta.fields)

另外,如果您要将模型导出到另一个库,则此解决方案与对查询的列表理解一起可以提供一个不错的解决方案。例如,将模型转储到pandas数据框中:

pandas_awesomeness = pd.DataFrame([m.as_dict() for m in SomeModel.objects.all()])

Lots of interesting solutions here. My solution was to add an as_dict method to my model with a dict comprehension.

def as_dict(self):
    return dict((f.name, getattr(self, f.name)) for f in self._meta.fields)

As a bonus, this solution paired with an list comprehension over a query makes for a nice solution if you want export your models to another library. For example, dumping your models into a pandas dataframe:

pandas_awesomeness = pd.DataFrame([m.as_dict() for m in SomeModel.objects.all()])

回答 7

(并非要发表评论)

好的,它并不是真的那样依赖类型。我可能对这里的原始问题有误解,因此请原谅。如果创建serliazers.py,则在其中创建具有元类的类。

Class MyModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = modelName
        fields =('csv','of','fields')

然后,当您在视图类中获取数据时,您可以:

model_data - Model.objects.filter(...)
serializer = MyModelSerializer(model_data, many=True)
return Response({'data': serilaizer.data}, status=status.HTTP_200_OK)

这在很多地方都差不多,它通过JSONRenderer返回了不错的JSON。

正如我所说的,这是DjangoRestFramework的礼貌,因此值得研究。

(did not mean to make the comment)

Ok, it doesn’t really depend on types in that way. I may have mis-understood the original question here so forgive me if that is the case. If you create serliazers.py then in there you create classes that have meta classes.

Class MyModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = modelName
        fields =('csv','of','fields')

Then when you get the data in the view class you can:

model_data - Model.objects.filter(...)
serializer = MyModelSerializer(model_data, many=True)
return Response({'data': serilaizer.data}, status=status.HTTP_200_OK)

That is pretty much what I have in a vareity of places and it returns nice JSON via the JSONRenderer.

As I said this is courtesy of the DjangoRestFramework so it’s worth looking into that.


回答 8

更简单的方法是只使用pprint,这在基本Python中

import pprint
item = MyDjangoModel.objects.get(name = 'foo')
pprint.pprint(item.__dict__, indent = 4)

这给出的输出类似于,json.dumps(..., indent = 4)但可以正确处理可能嵌入在模型实例中的怪异数据类型,例如ModelStateUUID

在Python 3.7上测试

The easier way is to just use pprint, which is in base Python

import pprint
item = MyDjangoModel.objects.get(name = 'foo')
pprint.pprint(item.__dict__, indent = 4)

This gives output that looks similar to json.dumps(..., indent = 4) but it correctly handles the weird data types that might be embedded in your model instance, such as ModelState and UUID, etc.

Tested on Python 3.7


回答 9

也许这对您有帮助。也许这并不能掩盖很多对很多的关系,但是当您要以json格式发送模型时,它非常方便。

def serial_model(modelobj):
  opts = modelobj._meta.fields
  modeldict = model_to_dict(modelobj)
  for m in opts:
    if m.is_relation:
        foreignkey = getattr(modelobj, m.name)
        if foreignkey:
            try:
                modeldict[m.name] = serial_model(foreignkey)
            except:
                pass
  return modeldict

Maybe this help you. May this not covert many to many relantionship, but es pretty handy when you want to send your model in json format.

def serial_model(modelobj):
  opts = modelobj._meta.fields
  modeldict = model_to_dict(modelobj)
  for m in opts:
    if m.is_relation:
        foreignkey = getattr(modelobj, m.name)
        if foreignkey:
            try:
                modeldict[m.name] = serial_model(foreignkey)
            except:
                pass
  return modeldict

回答 10

您见过的最佳解决方案。

将django.db.models.Model实例以及所有相关的ForeignKey,ManyToManyField和@Property函数字段转换为dict。

"""
Convert django.db.models.Model instance and all related ForeignKey, ManyToManyField and @property function fields into dict.
Usage:
    class MyDjangoModel(... PrintableModel):
        to_dict_fields = (...)
        to_dict_exclude = (...)
        ...
    a_dict = [inst.to_dict(fields=..., exclude=...) for inst in MyDjangoModel.objects.all()]
"""
import typing

import django.core.exceptions
import django.db.models
import django.forms.models


def get_decorators_dir(cls, exclude: typing.Optional[set]=None) -> set:
    """
    Ref: /programming/4930414/how-can-i-introspect-properties-and-model-fields-in-django
    :param exclude: set or None
    :param cls:
    :return: a set of decorators
    """
    default_exclude = {"pk", "objects"}
    if not exclude:
        exclude = default_exclude
    else:
        exclude = exclude.union(default_exclude)

    return set([name for name in dir(cls) if name not in exclude and isinstance(getattr(cls, name), property)])


class PrintableModel(django.db.models.Model):

    class Meta:
        abstract = True

    def __repr__(self):
        return str(self.to_dict())

    def to_dict(self, fields: typing.Optional[typing.Iterable]=None, exclude: typing.Optional[typing.Iterable]=None):
        opts = self._meta
        data = {}

        # support fields filters and excludes
        if not fields:
            fields = set()
        else:
            fields = set(fields)
        default_fields = getattr(self, "to_dict_fields", set())
        fields = fields.union(default_fields)

        if not exclude:
            exclude = set()
        else:
            exclude = set(exclude)
        default_exclude = getattr(self, "to_dict_exclude", set())
        exclude = exclude.union(default_exclude)

        # support syntax "field__childField__..."
        self_fields = set()
        child_fields = dict()
        if fields:
            for i in fields:
                splits = i.split("__")
                if len(splits) == 1:
                    self_fields.add(splits[0])
                else:
                    self_fields.add(splits[0])

                    field_name = splits[0]
                    child_fields.setdefault(field_name, set())
                    child_fields[field_name].add("__".join(splits[1:]))

        self_exclude = set()
        child_exclude = dict()
        if exclude:
            for i in exclude:
                splits = i.split("__")
                if len(splits) == 1:
                    self_exclude.add(splits[0])
                else:
                    field_name = splits[0]
                    if field_name not in child_exclude:
                        child_exclude[field_name] = set()
                    child_exclude[field_name].add("__".join(splits[1:]))

        for f in opts.concrete_fields + opts.many_to_many:
            if self_fields and f.name not in self_fields:
                continue
            if self_exclude and f.name in self_exclude:
                continue

            if isinstance(f, django.db.models.ManyToManyField):
                if self.pk is None:
                    data[f.name] = []
                else:
                    result = []
                    m2m_inst = f.value_from_object(self)
                    for obj in m2m_inst:
                        if isinstance(PrintableModel, obj) and hasattr(obj, "to_dict"):
                            d = obj.to_dict(
                                fields=child_fields.get(f.name),
                                exclude=child_exclude.get(f.name),
                            )
                        else:
                            d = django.forms.models.model_to_dict(
                                obj,
                                fields=child_fields.get(f.name),
                                exclude=child_exclude.get(f.name)
                            )
                        result.append(d)
                    data[f.name] = result
            elif isinstance(f, django.db.models.ForeignKey):
                if self.pk is None:
                    data[f.name] = []
                else:
                    data[f.name] = None
                    try:
                        foreign_inst = getattr(self, f.name)
                    except django.core.exceptions.ObjectDoesNotExist:
                        pass
                    else:
                        if isinstance(foreign_inst, PrintableModel) and hasattr(foreign_inst, "to_dict"):
                            data[f.name] = foreign_inst.to_dict(
                                fields=child_fields.get(f.name),
                                exclude=child_exclude.get(f.name)
                            )
                        elif foreign_inst is not None:
                            data[f.name] = django.forms.models.model_to_dict(
                                foreign_inst,
                                fields=child_fields.get(f.name),
                                exclude=child_exclude.get(f.name),
                            )

            elif isinstance(f, (django.db.models.DateTimeField, django.db.models.DateField)):
                v = f.value_from_object(self)
                if v is not None:
                    data[f.name] = v.isoformat()
                else:
                    data[f.name] = None
            else:
                data[f.name] = f.value_from_object(self)

        # support @property decorator functions
        decorator_names = get_decorators_dir(self.__class__)
        for name in decorator_names:
            if self_fields and name not in self_fields:
                continue
            if self_exclude and name in self_exclude:
                continue

            value = getattr(self, name)
            if isinstance(value, PrintableModel) and hasattr(value, "to_dict"):
                data[name] = value.to_dict(
                    fields=child_fields.get(name),
                    exclude=child_exclude.get(name)
                )
            elif hasattr(value, "_meta"):
                # make sure it is a instance of django.db.models.fields.Field
                data[name] = django.forms.models.model_to_dict(
                    value,
                    fields=child_fields.get(name),
                    exclude=child_exclude.get(name),
                )
            elif isinstance(value, (set, )):
                data[name] = list(value)
            else:
                data[name] = value

        return data

https://gist.github.com/shuge/f543dc2094a3183f69488df2bfb51a52

Best solution you have ever see.

Convert django.db.models.Model instance and all related ForeignKey, ManyToManyField and @Property function fields into dict.

"""
Convert django.db.models.Model instance and all related ForeignKey, ManyToManyField and @property function fields into dict.
Usage:
    class MyDjangoModel(... PrintableModel):
        to_dict_fields = (...)
        to_dict_exclude = (...)
        ...
    a_dict = [inst.to_dict(fields=..., exclude=...) for inst in MyDjangoModel.objects.all()]
"""
import typing

import django.core.exceptions
import django.db.models
import django.forms.models


def get_decorators_dir(cls, exclude: typing.Optional[set]=None) -> set:
    """
    Ref: https://stackoverflow.com/questions/4930414/how-can-i-introspect-properties-and-model-fields-in-django
    :param exclude: set or None
    :param cls:
    :return: a set of decorators
    """
    default_exclude = {"pk", "objects"}
    if not exclude:
        exclude = default_exclude
    else:
        exclude = exclude.union(default_exclude)

    return set([name for name in dir(cls) if name not in exclude and isinstance(getattr(cls, name), property)])


class PrintableModel(django.db.models.Model):

    class Meta:
        abstract = True

    def __repr__(self):
        return str(self.to_dict())

    def to_dict(self, fields: typing.Optional[typing.Iterable]=None, exclude: typing.Optional[typing.Iterable]=None):
        opts = self._meta
        data = {}

        # support fields filters and excludes
        if not fields:
            fields = set()
        else:
            fields = set(fields)
        default_fields = getattr(self, "to_dict_fields", set())
        fields = fields.union(default_fields)

        if not exclude:
            exclude = set()
        else:
            exclude = set(exclude)
        default_exclude = getattr(self, "to_dict_exclude", set())
        exclude = exclude.union(default_exclude)

        # support syntax "field__childField__..."
        self_fields = set()
        child_fields = dict()
        if fields:
            for i in fields:
                splits = i.split("__")
                if len(splits) == 1:
                    self_fields.add(splits[0])
                else:
                    self_fields.add(splits[0])

                    field_name = splits[0]
                    child_fields.setdefault(field_name, set())
                    child_fields[field_name].add("__".join(splits[1:]))

        self_exclude = set()
        child_exclude = dict()
        if exclude:
            for i in exclude:
                splits = i.split("__")
                if len(splits) == 1:
                    self_exclude.add(splits[0])
                else:
                    field_name = splits[0]
                    if field_name not in child_exclude:
                        child_exclude[field_name] = set()
                    child_exclude[field_name].add("__".join(splits[1:]))

        for f in opts.concrete_fields + opts.many_to_many:
            if self_fields and f.name not in self_fields:
                continue
            if self_exclude and f.name in self_exclude:
                continue

            if isinstance(f, django.db.models.ManyToManyField):
                if self.pk is None:
                    data[f.name] = []
                else:
                    result = []
                    m2m_inst = f.value_from_object(self)
                    for obj in m2m_inst:
                        if isinstance(PrintableModel, obj) and hasattr(obj, "to_dict"):
                            d = obj.to_dict(
                                fields=child_fields.get(f.name),
                                exclude=child_exclude.get(f.name),
                            )
                        else:
                            d = django.forms.models.model_to_dict(
                                obj,
                                fields=child_fields.get(f.name),
                                exclude=child_exclude.get(f.name)
                            )
                        result.append(d)
                    data[f.name] = result
            elif isinstance(f, django.db.models.ForeignKey):
                if self.pk is None:
                    data[f.name] = []
                else:
                    data[f.name] = None
                    try:
                        foreign_inst = getattr(self, f.name)
                    except django.core.exceptions.ObjectDoesNotExist:
                        pass
                    else:
                        if isinstance(foreign_inst, PrintableModel) and hasattr(foreign_inst, "to_dict"):
                            data[f.name] = foreign_inst.to_dict(
                                fields=child_fields.get(f.name),
                                exclude=child_exclude.get(f.name)
                            )
                        elif foreign_inst is not None:
                            data[f.name] = django.forms.models.model_to_dict(
                                foreign_inst,
                                fields=child_fields.get(f.name),
                                exclude=child_exclude.get(f.name),
                            )

            elif isinstance(f, (django.db.models.DateTimeField, django.db.models.DateField)):
                v = f.value_from_object(self)
                if v is not None:
                    data[f.name] = v.isoformat()
                else:
                    data[f.name] = None
            else:
                data[f.name] = f.value_from_object(self)

        # support @property decorator functions
        decorator_names = get_decorators_dir(self.__class__)
        for name in decorator_names:
            if self_fields and name not in self_fields:
                continue
            if self_exclude and name in self_exclude:
                continue

            value = getattr(self, name)
            if isinstance(value, PrintableModel) and hasattr(value, "to_dict"):
                data[name] = value.to_dict(
                    fields=child_fields.get(name),
                    exclude=child_exclude.get(name)
                )
            elif hasattr(value, "_meta"):
                # make sure it is a instance of django.db.models.fields.Field
                data[name] = django.forms.models.model_to_dict(
                    value,
                    fields=child_fields.get(name),
                    exclude=child_exclude.get(name),
                )
            elif isinstance(value, (set, )):
                data[name] = list(value)
            else:
                data[name] = value

        return data

https://gist.github.com/shuge/f543dc2094a3183f69488df2bfb51a52


回答 11

@zags的回答很全面,应该足够了,但是#5方法(这是IMO最好的方法)抛出错误,因此我改进了辅助函数。

由于OP请求转换many_to_many领域成主键,而不是对象的列表清单,我增强了功能,所以返回值现在为JSON序列化-通过将datetime物体进入strmany_to_many对象ID的列表。

import datetime

def ModelToDict(instance):
    '''
    Returns a dictionary object containing complete field-value pairs of the given instance

    Convertion rules:

        datetime.date --> str
        many_to_many --> list of id's

    '''

    concrete_fields = instance._meta.concrete_fields
    m2m_fields = instance._meta.many_to_many
    data = {}

    for field in concrete_fields:
        key = field.name
        value = field.value_from_object(instance)
        if type(value) == datetime.datetime:
            value = str(field.value_from_object(instance))
        data[key] = value

    for field in m2m_fields:
        key = field.name
        value = field.value_from_object(instance)
        data[key] = [rel.id for rel in value]

    return data

The answer from @zags is comprehensive and should suffice but the #5 method (which is the best one IMO) throws an error so I improved the helper function.

As the OP requested for converting many_to_many fields into a list of primary keys rather than a list of objects, I enhanced the function so the return value is now JSON serializable – by converting datetime objects into str and many_to_many objects to a list of id’s.

import datetime

def ModelToDict(instance):
    '''
    Returns a dictionary object containing complete field-value pairs of the given instance

    Convertion rules:

        datetime.date --> str
        many_to_many --> list of id's

    '''

    concrete_fields = instance._meta.concrete_fields
    m2m_fields = instance._meta.many_to_many
    data = {}

    for field in concrete_fields:
        key = field.name
        value = field.value_from_object(instance)
        if type(value) == datetime.datetime:
            value = str(field.value_from_object(instance))
        data[key] = value

    for field in m2m_fields:
        key = field.name
        value = field.value_from_object(instance)
        data[key] = [rel.id for rel in value]

    return data

Django OpenID的最佳解决方案是什么?[关闭]

问题:Django OpenID的最佳解决方案是什么?[关闭]

请注意:这是一个古老的问题,带有古老的答案。现在,大多数链接的应用程序都不再需要维护。这些天来,大多数人似乎使用django-allauthpython-social-auth。为了后代的缘故,下面将完整保留原始问题。


至少有六打Django应用程序为Django提供OpenID身份验证:

我和其中几个一起玩。西蒙·威利森(Simon Willison)的django-openid给人留下了深刻的印象,但由于他处于Djangoland趋势设定的最前沿,所以有时我很难理解他的趋势(例如django-openid中的整个动态urlpatterns系统)。而且,我无法登录才能与Google一起使用。

django-authopenid给人很好的印象,并且似乎与django-registration具有良好的集成。django-socialauthdjango-socialregistration支持Twitter和Facebook,这绝对是一个加号。谁知道Facebook是否以及何时开始成为OpenID提供者…?但是,socialauth似乎也有一些问题

那么,最好的OpenID应用程序是什么?请分享任何正面(和负面)经验。谢谢!

Please note: this is an ancient question with ancient answers. Most of the linked apps are now unmaintained. These days, most people seem to use django-allauth or python-social-auth. I’ll leave the original question intact below for posterity’s sake.


There are at least half a dozen Django apps that provide OpenID authentication for Django:

I played around with a couple of them. Simon Willison’s django-openid made a good impression, but as he is at the forefront of trendsetting in Djangoland, I sometimes have difficulties wrapping my head around his trends (e.g. the whole dynamic urlpatterns system in django-openid). What’s more, I couldn’t get login to work with Google.

django-authopenid made a good impression, and it seems to have good integration with django-registration. django-socialauth and django-socialregistration have support for Twitter and Facebook, which is definitely a plus. Who knows if and when Facebook will start to be an OpenID provider…? socialauth seems to have its share of problems, though.

So, what is the best OpenID app out there? Please share any positive (and negative) experience. Thanks!


回答 0

事实证明,该软件最适合我,而且似乎是最新的,它已在Launchpad上结束了。

它与已经利用django.auth模块的应用程序无缝集成。

https://launchpad.net/django-openid-auth

要获得运行副本:

bzr branch lp:django-openid-auth

或通过PyPI安装

pip install django-openid-auth

The one that has proven to work best for me, and which seems most up-to-date is the one over at launchpad.

It integrated seamlessly with my application that already utilizes the django.auth module.

https://launchpad.net/django-openid-auth

To get a copy run:

bzr branch lp:django-openid-auth

Or install it via PyPI

pip install django-openid-auth

回答 1

该主题的最后一篇文章是二月份。已经快8个月了,我可以肯定很多事情已经改变了。

我对Django-Socialauth非常感兴趣,因为它支持gmail,yahoo,facebook,twitter和OpenID。

我发现了两个看似最新的叉子:

https://github.com/uswaretech/Django-Socialauth

https://github.com/agiliq/Django-Socialauth

此刻第二个分叉最近已更新。

我想知道是否有人最近使用过这些叉子?我正在寻找最可靠的网站。

谢谢

更新:最新的fork似乎是omab / django-social-auth,这也是pypi包所指向的。

The last post for this thread is in February. It’s been almost 8 months and I’m pretty sure a lot of things have been changed.

I am very interested in Django-Socialauth since it supports gmail, yahoo, facebook, twitter, and OpenID.

I found two forks that seem up-to-date:

https://github.com/uswaretech/Django-Socialauth

https://github.com/agiliq/Django-Socialauth

The second fork has been recently updated at this moment.

I was wondering if anyone has recently used any of these forks? I am looking for the most reliable one for my website.

Thanks

Update: The most up-to-date fork appears to be omab/django-social-auth, which is also what the pypi package points at.


回答 2

我更喜欢django-authopenid,但是我认为大多数成熟的解决方案在这一点上是相当平等的。不过,这是我认为使用最多的东西。我已经对如何使用它进行了一些自定义,而无需实际进行分叉,这在我的书中是一个很大的优点。换句话说,它相当可钩。

I prefer django-authopenid, but I think most of the mature solutions are pretty equal at this point. Still, it is what I see used the most. I’ve made a handful of customizations to how we use it without having to actually fork it, and that’s a huge plus in my book. In other words, its fairly hookable.


回答 3

别忘了 Elf Sternberg的 django-socialauth分支-他正在努力清理他认为原始的socialauth应用程序中许多不良实施决策的内容。到目前为止看起来还不错,但尚不清楚他的项目是否会取得进展。

Don’t forget Elf Sternberg’s fork of django-socialauth – he’s working to clean up what he sees as a lot of bad implementation decisions in the original socialauth app. Looks clean so far but it’s unclear whether his project will have momentum.


回答 4

django-socialauth对我有好处

django-socialauth is good for me


回答 5

您可以尝试pinax


最喜欢的Django提示和功能?

问题:最喜欢的Django提示和功能?

受问题系列“ …的隐藏功能”的启发,我很想知道您最喜欢的Django提示或您所知的鲜为人知但有用的功能。

  • 请每个答案仅包含一个提示。
  • 添加Django版本要求(如果有)。

Inspired by the question series ‘Hidden features of …’, I am curious to hear about your favorite Django tips or lesser known but useful features you know of.

  • Please, include only one tip per answer.
  • Add Django version requirements if there are any.

回答 0

我将从我自己的提示开始:)

在settings.py中使用os.path.dirname()避免使用硬编码的目录名。

如果要在其他位置运行项目,请不要在settings.py中硬编码路径。如果您的模板和静态文件位于Django项目目录中,请在settings.py中使用以下代码:

# settings.py
import os
PROJECT_DIR = os.path.dirname(__file__)
...
STATIC_DOC_ROOT = os.path.join(PROJECT_DIR, "static")
...
TEMPLATE_DIRS = (
    os.path.join(PROJECT_DIR, "templates"),
)

鸣谢:我从屏幕录像“ Django From the Ground Up ” 获得了这个技巧。

I’m just going to start with a tip from myself :)

Use os.path.dirname() in settings.py to avoid hardcoded dirnames.

Don’t hardcode path’s in your settings.py if you want to run your project in different locations. Use the following code in settings.py if your templates and static files are located within the Django project directory:

# settings.py
import os
PROJECT_DIR = os.path.dirname(__file__)
...
STATIC_DOC_ROOT = os.path.join(PROJECT_DIR, "static")
...
TEMPLATE_DIRS = (
    os.path.join(PROJECT_DIR, "templates"),
)

Credits: I got this tip from the screencast ‘Django From the Ground Up‘.


回答 1

安装Django Command Extensionspygraphviz,然后发出以下命令以获取非常漂亮的Django模型可视化效果:

./manage.py graph_models -a -g -o my_project.png

Install Django Command Extensions and pygraphviz and then issue the following command to get a really nice looking Django model visualization:

./manage.py graph_models -a -g -o my_project.png

回答 2

使用django-annoying的 render_to装饰器代替render_to_response

@render_to('template.html')
def foo(request):
    bars = Bar.objects.all()
    if request.user.is_authenticated():
        return HttpResponseRedirect("/some/url/")
    else:
        return {'bars': bars}

# equals to
def foo(request):
    bars = Bar.objects.all()
    if request.user.is_authenticated():
        return HttpResponseRedirect("/some/url/")
    else:
        return render_to_response('template.html',
                              {'bars': bars},
                              context_instance=RequestContext(request))

编辑指出,返回HttpResponse(例如重定向)将使装饰器短路并按预期工作。

Use django-annoying’s render_to decorator instead of render_to_response.

@render_to('template.html')
def foo(request):
    bars = Bar.objects.all()
    if request.user.is_authenticated():
        return HttpResponseRedirect("/some/url/")
    else:
        return {'bars': bars}

# equals to
def foo(request):
    bars = Bar.objects.all()
    if request.user.is_authenticated():
        return HttpResponseRedirect("/some/url/")
    else:
        return render_to_response('template.html',
                              {'bars': bars},
                              context_instance=RequestContext(request))

Edited to point out that returning an HttpResponse (such as a redirect) will short circuit the decorator and work just as you expect.


回答 3

我在网站的所有模板上使用了一组自定义标签。在寻找一种自动加载的方式(DRY,还记得吗?),我发现了以下内容:

from django import template
template.add_to_builtins('project.app.templatetags.custom_tag_module')

如果将其放在默认情况下加载的模块(例如您的主urlconf)中,则可以在任何模板中使用自定义标签模块中的标签和过滤器,而无需使用 {% load custom_tag_module %}

传递给的参数template.add_to_builtins()可以是任何模块路径;您的自定义标签模块不必位于特定的应用程序中。例如,它也可以是项目根目录中的模块(例如'project.custom_tag_module')。

There’s a set of custom tags I use all over my site’s templates. Looking for a way to autoload it (DRY, remember?), I found the following:

from django import template
template.add_to_builtins('project.app.templatetags.custom_tag_module')

If you put this in a module that’s loaded by default (your main urlconf for instance), you’ll have the tags and filters from your custom tag module available in any template, without using {% load custom_tag_module %}.

The argument passed to template.add_to_builtins() can be any module path; your custom tag module doesn’t have to live in a specific application. For example, it can also be a module in your project’s root directory (eg. 'project.custom_tag_module').


回答 4

如果您正在处理多个Django项目,则Virtualenv + Python = life saver,并且它们有可能不都依赖于同一版本的Django /应用程序。

Virtualenv + Python = life saver if you are working on multiple Django projects and there is a possibility that they all don’t depend on the same version of Django/an application.


回答 5

不要对您的网址进行硬编码!

请改用网址名称,然后reverse函数来获取URL本身。

定义URL映射时,请为URL命名。

urlpatterns += ('project.application.views'
   url( r'^something/$', 'view_function', name="url-name" ),
   ....
)

确保每个URL的名称都是唯一的。

我通常使用一致的格式“ project-appplication-view”,例如用于线程视图的“ cbx-forum-thread”。

更新(无耻地窃取ayaz的附加内容):

可以在带有url标记的模板中使用该名称。

Don’t hard-code your URLs!

Use url names instead, and the reverse function to get the URL itself.

When you define your URL mappings, give names to your URLs.

urlpatterns += ('project.application.views'
   url( r'^something/$', 'view_function', name="url-name" ),
   ....
)

Make sure the name is unique per URL.

I usually have a consistent format “project-appplication-view”, e.g. “cbx-forum-thread” for a thread view.

UPDATE (shamelessly stealing ayaz’s addition):

This name can be used in templates with the url tag.


回答 6

使用django调试工具栏。例如,它允许查看在呈现视图时执行的所有SQL查询,还可以查看其中任何一个的stacktrace。

Use django debug toolbar. For example, it allows to view all SQL queries performed while rendering view and you can also view stacktrace for any of them.


回答 7

不要编写自己的登录页面。如果您使用的是django.contrib.auth。

真正的肮脏秘密是,如果您还使用django.contrib.admin,并且django.template.loaders.app_directories.load_template_source位于模板加载器中, 那么您也可以免费获得模板!

# somewhere in urls.py
urlpatterns += patterns('django.contrib.auth',
    (r'^accounts/login/$','views.login', {'template_name': 'admin/login.html'}),
    (r'^accounts/logout/$','views.logout'),
)

Don’t write your own login pages. If you’re using django.contrib.auth.

The real, dirty secret is that if you’re also using django.contrib.admin, and django.template.loaders.app_directories.load_template_source is in your template loaders, you can get your templates free too!

# somewhere in urls.py
urlpatterns += patterns('django.contrib.auth',
    (r'^accounts/login/$','views.login', {'template_name': 'admin/login.html'}),
    (r'^accounts/logout/$','views.logout'),
)

回答 8

上下文处理器很棒。

假设您有一个不同的用户模型,并且希望将其包含在每个响应中。而不是这样做:

def myview(request, arg, arg2=None, template='my/template.html'):
    ''' My view... '''
    response = dict()
    myuser = MyUser.objects.get(user=request.user)
    response['my_user'] = myuser
    ...
    return render_to_response(template,
                              response,
                              context_instance=RequestContext(request))

上下文过程使您能够将任何变量传递到模板。我通常把我的放在'my_project/apps/core/context.py

def my_context(request):
    try:
        return dict(my_user=MyUser.objects.get(user=request.user))
    except ObjectNotFound:
        return dict(my_user='')

在您settings.py的代码中,将以下行添加到您的TEMPLATE_CONTEXT_PROCESSORS

TEMPLATE_CONTEXT_PROCESSORS = (
    'my_project.apps.core.context.my_context',
    ...
)

现在,每次发出请求时,它都会my_user自动包含密钥。

信号赢了。

几个月前,我写了一篇有关此的博客文章,所以我要剪切粘贴:

Django开箱即用地为您提供了许多非常有用的信号。您可以在保存,初始化,删除甚至处理请求之前和之后进行操作。因此,让我们远离这些概念并演示如何使用它们。说我们有一个博客

from django.utils.translation import ugettext_lazy as _
class Post(models.Model):
    title = models.CharField(_('title'), max_length=255)
    body = models.TextField(_('body'))
    created = models.DateTimeField(auto_now_add=True)

因此,您想以某种方式通知我们已发布新帖子的众多Blog Ping服务之一,重建最新的帖子缓存,并对其进行鸣叫。有了信号,您就可以执行所有这些操作而不必向Post类添加任何方法。

import twitter

from django.core.cache import cache
from django.db.models.signals import post_save
from django.conf import settings

def posted_blog(sender, created=None, instance=None, **kwargs):
    ''' Listens for a blog post to save and alerts some services. '''
    if (created and instance is not None):
        tweet = 'New blog post! %s' instance.title
        t = twitter.PostUpdate(settings.TWITTER_USER,
                               settings.TWITTER_PASSWD,
                               tweet)
        cache.set(instance.cache_key, instance, 60*5)
       # send pingbacks
       # ...
       # whatever else
    else:
        cache.delete(instance.cache_key)
post_save.connect(posted_blog, sender=Post)

通过定义该函数并使用post_init信号将函数连接到Post模型,然后在保存后执行该函数,我们就可以了。

Context processors are awesome.

Say you have a different user model and you want to include that in every response. Instead of doing this:

def myview(request, arg, arg2=None, template='my/template.html'):
    ''' My view... '''
    response = dict()
    myuser = MyUser.objects.get(user=request.user)
    response['my_user'] = myuser
    ...
    return render_to_response(template,
                              response,
                              context_instance=RequestContext(request))

Context processes give you the ability to pass any variable to your templates. I typically put mine in 'my_project/apps/core/context.py:

def my_context(request):
    try:
        return dict(my_user=MyUser.objects.get(user=request.user))
    except ObjectNotFound:
        return dict(my_user='')

In your settings.py add the following line to your TEMPLATE_CONTEXT_PROCESSORS

TEMPLATE_CONTEXT_PROCESSORS = (
    'my_project.apps.core.context.my_context',
    ...
)

Now every time a request is made it includes the my_user key automatically.

Also signals win.

I wrote a blog post about this a few months ago so I’m just going to cut and paste:

Out of the box Django gives you several signals that are incredibly useful. You have the ability to do things pre and post save, init, delete, or even when a request is being processed. So lets get away from the concepts and demonstrate how these are used. Say we’ve got a blog

from django.utils.translation import ugettext_lazy as _
class Post(models.Model):
    title = models.CharField(_('title'), max_length=255)
    body = models.TextField(_('body'))
    created = models.DateTimeField(auto_now_add=True)

So somehow you want to notify one of the many blog-pinging services we’ve made a new post, rebuild the most recent posts cache, and tweet about it. Well with signals you have the ability to do all of this without having to add any methods to the Post class.

import twitter

from django.core.cache import cache
from django.db.models.signals import post_save
from django.conf import settings

def posted_blog(sender, created=None, instance=None, **kwargs):
    ''' Listens for a blog post to save and alerts some services. '''
    if (created and instance is not None):
        tweet = 'New blog post! %s' instance.title
        t = twitter.PostUpdate(settings.TWITTER_USER,
                               settings.TWITTER_PASSWD,
                               tweet)
        cache.set(instance.cache_key, instance, 60*5)
       # send pingbacks
       # ...
       # whatever else
    else:
        cache.delete(instance.cache_key)
post_save.connect(posted_blog, sender=Post)

There we go, by defining that function and using the post_init signal to connect the function to the Post model and execute it after it has been saved.


回答 9

当我刚开始的时候,我不知道有一个分页器,请确保您知道它的存在!

When I was starting out, I didn’t know that there was a Paginator, make sure you know of its existence!!


回答 10

使用IPython可以进入任何级别的代码,并使用IPython的功能进行调试。一旦安装了IPython,就可以将此代码放到要调试的位置:

from IPython.Shell import IPShellEmbed; IPShellEmbed()()

然后,刷新页面,转到runserver窗口,您将进入交互式IPython窗口。

我在TextMate中设置了一个代码段,所以我只需键入ipshell并单击Tab。没有它,我活不下去。

Use IPython to jump into your code at any level and debug using the power of IPython. Once you have installed IPython just put this code in wherever you want to debug:

from IPython.Shell import IPShellEmbed; IPShellEmbed()()

Then, refresh the page, go to your runserver window and you will be in an interactive IPython window.

I have a snippet set up in TextMate so I just type ipshell and hit tab. I couldn’t live without it.


回答 11

运行开发SMTP服务器,该服务器将仅输出发送给它的任何内容(如果您不想在开发服务器上实际安装SMTP)。

命令行:

python -m smtpd -n -c DebuggingServer localhost:1025

Run a development SMTP server that will just output whatever is sent to it (if you don’t want to actually install SMTP on your dev server.)

command line:

python -m smtpd -n -c DebuggingServer localhost:1025

回答 12

django-admin文档中

如果使用Bash shell,请考虑安装Django bash完成脚本,该脚本extras/django_bash_completion位于Django发行版中。它启用django-admin.pymanage.py命令的制表符补全,因此您可以例如…

  • 类型 django-admin.py
  • 按[TAB]查看所有可用选项。
  • 键入sql,然后按[TAB],以查看名称以开头的所有可用选项sql

From the django-admin documentation:

If you use the Bash shell, consider installing the Django bash completion script, which lives in extras/django_bash_completion in the Django distribution. It enables tab-completion of django-admin.py and manage.py commands, so you can, for instance…

  • Type django-admin.py.
  • Press [TAB] to see all available options.
  • Type sql, then [TAB], to see all available options whose names start with sql.

回答 13

./manage.py runserver_plus附带facilty django_extensions是真正真棒。

它创建了一个增强的调试页面,除其他外,该页面使用Werkzeug调试器为堆栈中的每个点创建交互式调试控制台(请参见屏幕截图)。它还提供了一种非常有用的便捷调试方法,dump()用于显示有关对象/框架的信息。

在此处输入图片说明

要安装,您可以使用pip:

pip install django_extensions
pip install Werkzeug

然后添加'django_extensions'到您的INSTALLED_APPS元组中,settings.py并使用新扩展名启动开发服务器:

./manage.py runserver_plus

这将改变您的调试方式。

The ./manage.py runserver_plus facilty which comes with django_extensions is truly awesome.

It creates an enhanced debug page that, amongst other things, uses the Werkzeug debugger to create interactive debugging consoles for each point in the stack (see screenshot). It also provides a very useful convenience debugging method dump() for displaying information about an object/frame.

enter image description here

To install, you can use pip:

pip install django_extensions
pip install Werkzeug

Then add 'django_extensions' to your INSTALLED_APPS tuple in settings.py and start the development server with the new extension:

./manage.py runserver_plus

This will change the way you debug.


回答 14

我喜欢使用Python调试器pdb调试Django项目。

这是学习使用方法的有用链接:http : //www.ferg.org/papers/debugging_in_python.html

I like to use the Python debugger pdb to debug Django projects.

This is a helpful link for learning how to use it: http://www.ferg.org/papers/debugging_in_python.html


回答 15

尝试在Django和另一个应用程序之间交换数据时,request.raw_post_data是个好朋友。用它来接收和定制处理XML数据。

文档:http : //docs.djangoproject.com/en/dev/ref/request-response/

When trying to exchange data between Django and another application, request.raw_post_data is a good friend. Use it to receive and custom-process, say, XML data.

Documentation: http://docs.djangoproject.com/en/dev/ref/request-response/


回答 16

使用Jinja2与Django一起。

如果您发现Django模板语言非常严格(像我一样!),那么您不必受其限制。Django非常灵活,并且模板语言与系统的其余部分松散耦合,因此只需插入另一种模板语言并使用它来呈现您的http响应!

我使用Jinja2,它几乎像django模板语言的功能强大的版本,它使用相同的语法,并允许您在if语句中使用表达式!不再制作自定义的if标签,例如if_item_in_list!你可以简单地说%{ if item in list %},或{% if object.field < 10 %}

但这还不是全部;它具有更多的功能来简化模板的创建,尽管这里没有全部介绍。

Use Jinja2 alongside Django.

If you find the Django template language extremely restricting (like me!) then you don’t have to be stuck with it. Django is flexible, and the template language is loosely coupled to the rest of the system, so just plug-in another template language and use it to render your http responses!

I use Jinja2, it’s almost like a powered-up version of the django template language, it uses the same syntax, and allows you to use expressions in if statements! no more making a custom if-tags such as if_item_in_list! you can simply say %{ if item in list %}, or {% if object.field < 10 %}.

But that’s not all; it has many more features to ease template creation, that I can’t go though all of them in here.


回答 17

assert False在您的视图代码中添加转储调试信息。

Add assert False in your view code to dump debug information.


回答 18

这增加了上面关于Django URL名称和反向URL调度的答复

URL名称也可以在模板中有效使用。例如,对于给定的URL模式:

url(r'(?P<project_id>\d+)/team/$', 'project_team', name='project_team')

您可以在模板中包含以下内容:

<a href="{% url project_team project.id %}">Team</a>

This adds to the reply above about Django URL names and reverse URL dispatching.

The URL names can also be effectively used within templates. For example, for a given URL pattern:

url(r'(?P<project_id>\d+)/team/$', 'project_team', name='project_team')

you can have the following in templates:

<a href="{% url project_team project.id %}">Team</a>

回答 19

由于Django“视图”仅需要返回HttpResponse的可调用对象,因此您可以轻松地创建基于类的视图,例如Ruby on Rails和其他框架中的视图。

有几种方法可以创建基于类的视图,这是我的最爱:

from django import http

class RestView(object):
    methods = ('GET', 'HEAD')

    @classmethod
    def dispatch(cls, request, *args, **kwargs):
        resource = cls()
        if request.method.lower() not in (method.lower() for method in resource.methods):
            return http.HttpResponseNotAllowed(resource.methods)
        try:
            method = getattr(resource, request.method.lower())
        except AttributeError:
            raise Exception("View method `%s` does not exist." % request.method.lower())
        if not callable(method):
            raise Exception("View method `%s` is not callable." % request.method.lower())
        return method(request, *args, **kwargs)

    def get(self, request, *args, **kwargs):
        return http.HttpResponse()

    def head(self, request, *args, **kwargs):
        response = self.get(request, *args, **kwargs)
        response.content = ''
        return response

您可以在基本视图中添加各种其他内容,例如条件请求处理和授权。

设置好视图后,您的urls.py将如下所示:

from django.conf.urls.defaults import *
from views import MyRestView

urlpatterns = patterns('',
    (r'^restview/', MyRestView.dispatch),
)

Since Django “views” only need to be callables that return an HttpResponse, you can easily create class-based views like those in Ruby on Rails and other frameworks.

There are several ways to create class-based views, here’s my favorite:

from django import http

class RestView(object):
    methods = ('GET', 'HEAD')

    @classmethod
    def dispatch(cls, request, *args, **kwargs):
        resource = cls()
        if request.method.lower() not in (method.lower() for method in resource.methods):
            return http.HttpResponseNotAllowed(resource.methods)
        try:
            method = getattr(resource, request.method.lower())
        except AttributeError:
            raise Exception("View method `%s` does not exist." % request.method.lower())
        if not callable(method):
            raise Exception("View method `%s` is not callable." % request.method.lower())
        return method(request, *args, **kwargs)

    def get(self, request, *args, **kwargs):
        return http.HttpResponse()

    def head(self, request, *args, **kwargs):
        response = self.get(request, *args, **kwargs)
        response.content = ''
        return response

You can add all sorts of other stuff like conditional request handling and authorization in your base view.

Once you’ve got your views setup your urls.py will look something like this:

from django.conf.urls.defaults import *
from views import MyRestView

urlpatterns = patterns('',
    (r'^restview/', MyRestView.dispatch),
)

回答 20

而不是使用render_to_response将上下文绑定到模板并将其呈现(这是Django文档通常显示的内容),而是使用通用视图direct_to_template。它的功能相同,render_to_response但是它还会自动将RequestContext添加到模板上下文中,从而隐式允许使用上下文处理器。您可以使用手动进行操作render_to_response,但是为什么要麻烦呢?这只是要记住的又一个步骤和另一个LOC。除了使用上下文处理器之外,在模板中添加RequestContext还可以使您执行以下操作:

<a href="{{MEDIA_URL}}images/frog.jpg">A frog</a> 

这非常有用。实际上,一般而言,+1是针对通用视图的。Django文档大多将它们显示为快捷方式,甚至没有简单应用程序的views.py文件,但您也可以在自己的视图函数中使用它们:

from django.views.generic import simple

def article_detail(request, slug=None):
    article = get_object_or_404(Article, slug=slug)
    return simple.direct_to_template(request, 
        template="articles/article_detail.html",
        extra_context={'article': article}
    )

Instead of using render_to_response to bind your context to a template and render it (which is what the Django docs usually show) use the generic view direct_to_template. It does the same thing that render_to_response does but it also automatically adds RequestContext to the template context, implicitly allowing context processors to be used. You can do this manually using render_to_response, but why bother? It’s just another step to remember and another LOC. Besides making use of context processors, having RequestContext in your template allows you to do things like:

<a href="{{MEDIA_URL}}images/frog.jpg">A frog</a> 

which is very useful. In fact, +1 on generic views in general. The Django docs mostly show them as shortcuts for not even having a views.py file for simple apps, but you can also use them inside your own view functions:

from django.views.generic import simple

def article_detail(request, slug=None):
    article = get_object_or_404(Article, slug=slug)
    return simple.direct_to_template(request, 
        template="articles/article_detail.html",
        extra_context={'article': article}
    )

回答 21

我没有足够的声誉来回答有问题的评论,但是请务必注意,如果您要使用Jinja,则它不支持模板块名称中的’-‘字符,而Django则支持。这给我带来了很多问题,并浪费了很多时间来跟踪它生成的非常模糊的错误消息。

I don’t have enough reputation to reply to the comment in question, but it’s important to note that if you’re going to use Jinja, it does NOT support the ‘-‘ character in template block names, while Django does. This caused me a lot of problems and wasted time trying to track down the very obscure error message it generated.


回答 22

开始设计您的网站时,webdesign应用程序非常有用。导入后,您可以添加以下内容以生成示例文本:

{% load webdesign %}
{% lorem 5 p %}

The webdesign app is very useful when starting to design your website. Once imported, you can add this to generate sample text:

{% load webdesign %}
{% lorem 5 p %}

回答 23

django.db.models.get_model 确实允许您检索模型而不导入它。

James展示了它的实用性“ Django技巧:编写更好的模板标签-迭代4”

django.db.models.get_model does allow you to retrieve a model without importing it.

James shows how handy it can be: “Django tips: Write better template tags — Iteration 4 “.


回答 24

每个人都知道有一个可以使用“ manage.py runserver”运行的开发服务器,但是您是否知道也有一个用于服务静态文件(CSS / JS / IMG)的开发视图?

新手总是感到困惑,因为Django并没有提供静态文件的任何方式。这是因为开发团队认为这是现实生活中的Web服务器的工作。

但是在开发时,您可能不想设置Apache + mod_wisgi,这很繁重。然后,您可以将以下内容添加到urls.py中:

(r'^site_media/(?P<path>.*)$', 'django.views.static.serve',
        {'document_root': '/path/to/media'}),

您的CSS / JS / IMG将在www.yoursite.com/site_media/上提供。

当然,不要在生产环境中使用它。

Everybody knows there is a development server you can run with “manage.py runserver”, but did you know that there is a development view for serving static files (CSS / JS / IMG) as well ?

Newcomers are always puzzled because Django doesn’t come with any way to serve static files. This is because the dev team think it is the job for a real life Web server.

But when developing, you may not want to set up Apache + mod_wisgi, it’s heavy. Then you can just add the following to urls.py:

(r'^site_media/(?P<path>.*)$', 'django.views.static.serve',
        {'document_root': '/path/to/media'}),

Your CSS / JS / IMG will be available at www.yoursite.com/site_media/.

Of course, don’t use it in a production environment.


回答 25

我是从soul-thumbnails的文档中学到的应用程序。您可以在模板标签中使用“ as”关键字,以在模板中的其他位置使用调用结果。

例如:

{% url image-processor uid as img_src %}
<img src="{% thumbnail img_src 100x100 %}"/>

在传递Django templatetag文档时提到了这一点,但仅引用了循环。他们并没有要求您也可以在其他任何地方使用此功能。

I learned this one from the documentation for the sorl-thumbnails app. You can use the “as” keyword in template tags to use the results of the call elsewhere in your template.

For example:

{% url image-processor uid as img_src %}
<img src="{% thumbnail img_src 100x100 %}"/>

This is mentioned in passing in the Django templatetag documentation, but in reference to loops only. They don’t call out that you can use this elsewhere (anywhere?) as well.


回答 26

django.views.generic.list_detail.object_list-它提供了用于分页的所有逻辑和模板变量(我已经写了数千次的苦工之一)。 包装它允许您需要任何逻辑。这个gem为我节省了很多时间在“搜索结果”页面中调试一次错误的调试,并在此过程中使视图代码更加整洁。

django.views.generic.list_detail.object_list — It provides all the logic & template variables for pagination (one of those I’ve-written-that-a-thousand-times-now drudgeries). Wrapping it allows for any logic you need. This gem has saved me many hours of debugging off-by-one errors in my “Search Results” pages and makes the view code cleaner in the process.


回答 27

PyCharm IDE是一个很好的代码编写环境,尤其是调试功能,并内置了对Django的支持。

PyCharm IDE is a nice environment to code and especially debug, with built-in support for Django.


回答 28

使用xml_models创建使用XML REST API后端(而不是SQL后端)的Django模型。这非常有用,尤其是在对第三方API建模时-您会获得与以前相同的所有QuerySet语法。您可以从PyPI安装它。

来自API的XML:

<profile id=4>
    <email>joe@example.com</email>
    <first_name>Joe</first_name>
    <last_name>Example</last_name>
    <date_of_birth>1975-05-15</date_of_birth>
</profile>

现在在python中:

class Profile(xml_models.Model):
    user_id = xml_models.IntField(xpath='/profile/@id')
    email = xml_models.CharField(xpath='/profile/email')
    first = xml_models.CharField(xpath='/profile/first_name')
    last = xml_models.CharField(xpath='/profile/last_name')
    birthday = xml_models.DateField(xpath='/profile/date_of_birth')

    finders = {
        (user_id,):  settings.API_URL +'/api/v1/profile/userid/%s',
        (email,):  settings.API_URL +'/api/v1/profile/email/%s',
    }

profile = Profile.objects.get(user_id=4)
print profile.email
# would print 'joe@example.com'

它还可以处理关系和集合。我们每天都在大量使用的生产代码中使用它,因此即使它是beta版,它也非常有用。它还具有一组可以在测试中使用的存根。

(免责声明:虽然我不是该库的作者,但我现在是一名提交者,做了一些次要的提交)

Use xml_models to create Django models that use an XML REST API backend (instead of a SQL one). This is very useful especially when modelling third party APIs – you get all the same QuerySet syntax that you’re used to. You can install it from PyPI.

XML from an API:

<profile id=4>
    <email>joe@example.com</email>
    <first_name>Joe</first_name>
    <last_name>Example</last_name>
    <date_of_birth>1975-05-15</date_of_birth>
</profile>

And now in python:

class Profile(xml_models.Model):
    user_id = xml_models.IntField(xpath='/profile/@id')
    email = xml_models.CharField(xpath='/profile/email')
    first = xml_models.CharField(xpath='/profile/first_name')
    last = xml_models.CharField(xpath='/profile/last_name')
    birthday = xml_models.DateField(xpath='/profile/date_of_birth')

    finders = {
        (user_id,):  settings.API_URL +'/api/v1/profile/userid/%s',
        (email,):  settings.API_URL +'/api/v1/profile/email/%s',
    }

profile = Profile.objects.get(user_id=4)
print profile.email
# would print 'joe@example.com'

It can also handle relationships and collections. We use it every day in heavily used production code, so even though it’s beta it’s very usable. It also has a good set of stubs that you can use in your tests.

(Disclaimer: while I’m not the author of this library, I am now a committer, having made a few minor commits)


回答 29

使用数据库迁移。使用南方

Use database migrations. Use South.


如何在Django中管理本地和生产设置?

问题:如何在Django中管理本地和生产设置?

建议使用什么方式处理本地开发和生产服务器的设置?它们中的某些(例如常量等)都可以更改/访问,但是其中一些(例如静态文件的路径)需要保持不同,因此,每次部署新代码时都不应覆盖它们。

当前,我将所有常量添加到中settings.py。但是每次我在本地更改一些常量时,都必须将其复制到生产服务器并编辑文件以进行生产特定更改… :(

编辑:这个问题似乎没有标准答案,我已经接受了最受欢迎的方法。

What is the recommended way of handling settings for local development and the production server? Some of them (like constants, etc) can be changed/accessed in both, but some of them (like paths to static files) need to remain different, and hence should not be overwritten every time the new code is deployed.

Currently, I am adding all constants to settings.py. But every time I change some constant locally, I have to copy it to the production server and edit the file for production specific changes… :(

Edit: looks like there is no standard answer to this question, I’ve accepted the most popular method.


回答 0

settings.py

try:
    from local_settings import *
except ImportError as e:
    pass

您可以覆盖local_settings.py;中的需要;然后,它应该脱离版本控制。但是由于您提到了复制,我猜您没有使用;)

In settings.py:

try:
    from local_settings import *
except ImportError as e:
    pass

You can override what needed in local_settings.py; it should stay out of your version control then. But since you mention copying I’m guessing you use none ;)


回答 1

Django的两个摘要:Django 1.5最佳实践建议对您的设置文件使用版本控制并将文件存储在单独的目录中:

project/
    app1/
    app2/
    project/
        __init__.py
        settings/
            __init__.py
            base.py
            local.py
            production.py
    manage.py

base.py文件包含常用的设置(如MEDIA_ROOT或ADMIN),而local.pyproduction.py有网站特定的设置:

在基本文件中settings/base.py

INSTALLED_APPS = (
    # common apps...
)

在本地开发设置文件中settings/local.py

from project.settings.base import *

DEBUG = True
INSTALLED_APPS += (
    'debug_toolbar', # and other apps for local development
)

在文件生产设置文件中settings/production.py

from project.settings.base import *

DEBUG = False
INSTALLED_APPS += (
    # other apps for production site
)

然后,在运行django时,添加以下--settings选项:

# Running django for local development
$ ./manage.py runserver 0:8000 --settings=project.settings.local

# Running django shell on the production site
$ ./manage.py shell --settings=project.settings.production

该书的作者还在Github上提供了一个示例项目布局模板

Two Scoops of Django: Best Practices for Django 1.5 suggests using version control for your settings files and storing the files in a separate directory:

project/
    app1/
    app2/
    project/
        __init__.py
        settings/
            __init__.py
            base.py
            local.py
            production.py
    manage.py

The base.py file contains common settings (such as MEDIA_ROOT or ADMIN), while local.py and production.py have site-specific settings:

In the base file settings/base.py:

INSTALLED_APPS = (
    # common apps...
)

In the local development settings file settings/local.py:

from project.settings.base import *

DEBUG = True
INSTALLED_APPS += (
    'debug_toolbar', # and other apps for local development
)

In the file production settings file settings/production.py:

from project.settings.base import *

DEBUG = False
INSTALLED_APPS += (
    # other apps for production site
)

Then when you run django, you add the --settings option:

# Running django for local development
$ ./manage.py runserver 0:8000 --settings=project.settings.local

# Running django shell on the production site
$ ./manage.py shell --settings=project.settings.production

The authors of the book have also put up a sample project layout template on Github.


回答 2

代替settings.py使用以下布局:

.
└── settings/
    ├── __init__.py  <= not versioned
    ├── common.py
    ├── dev.py
    └── prod.py

common.py 是大多数配置的所在地。

prod.py 从common导入所有内容,并覆盖需要覆盖的所有内容:

from __future__ import absolute_import # optional, but I like it
from .common import *

# Production overrides
DEBUG = False
#...

同样,dev.py从中导入所有内容common.py并覆盖其需要覆盖的所有内容。

最后,__init__.py是您决定加载哪些设置的地方,也是存储机密的地方(因此,不应对该文件进行版本控制):

from __future__ import absolute_import
from .prod import *  # or .dev if you want dev

##### DJANGO SECRETS
SECRET_KEY = '(3gd6shenud@&57...'
DATABASES['default']['PASSWORD'] = 'f9kGH...'

##### OTHER SECRETS
AWS_SECRET_ACCESS_KEY = "h50fH..."

我喜欢这个解决方案的地方是:

  1. 除秘密外,一切都在您的版本控制系统中
  2. 大多数配置都集中在一个位置:common.py
  3. 特定于产品的东西进入了prod.py,特定于开发器的东西进入了dev.py。这很简单。
  4. 您可以从覆盖的东西common.pyprod.py或者dev.py,你可以覆盖任何东西__init__.py
  5. 这是简单易懂的python。没有重新导入的黑客。

Instead of settings.py, use this layout:

.
└── settings/
    ├── __init__.py  <= not versioned
    ├── common.py
    ├── dev.py
    └── prod.py

common.py is where most of your configuration lives.

prod.py imports everything from common, and overrides whatever it needs to override:

from __future__ import absolute_import # optional, but I like it
from .common import *

# Production overrides
DEBUG = False
#...

Similarly, dev.py imports everything from common.py and overrides whatever it needs to override.

Finally, __init__.py is where you decide which settings to load, and it’s also where you store secrets (therefore this file should not be versioned):

from __future__ import absolute_import
from .prod import *  # or .dev if you want dev

##### DJANGO SECRETS
SECRET_KEY = '(3gd6shenud@&57...'
DATABASES['default']['PASSWORD'] = 'f9kGH...'

##### OTHER SECRETS
AWS_SECRET_ACCESS_KEY = "h50fH..."

What I like about this solution is:

  1. Everything is in your versioning system, except secrets
  2. Most configuration is in one place: common.py.
  3. Prod-specific things go in prod.py, dev-specific things go in dev.py. It’s simple.
  4. You can override stuff from common.py in prod.py or dev.py, and you can override anything in __init__.py.
  5. It’s straightforward python. No re-import hacks.

回答 3

我使用Harper Shelby发布的“ if DEBUG”样式的设置的稍微修改的版本。显然,取决于环境(win / linux / etc),可能需要对代码进行一些调整。

我以前使用的是“ if DEBUG”,但发现偶尔需要将DEUBG设置为False进行测试。我真正想区分的是环境是生产环境还是开发环境,这使我可以自由选择DEBUG级别。

PRODUCTION_SERVERS = ['WEBSERVER1','WEBSERVER2',]
if os.environ['COMPUTERNAME'] in PRODUCTION_SERVERS:
    PRODUCTION = True
else:
    PRODUCTION = False

DEBUG = not PRODUCTION
TEMPLATE_DEBUG = DEBUG

# ...

if PRODUCTION:
    DATABASE_HOST = '192.168.1.1'
else:
    DATABASE_HOST = 'localhost'

我仍然会考虑以这种方式设置正在进行的工作。我还没有一种方法可以处理涵盖所有基础的Django设置,但同时也没有设置的麻烦(我对5x设置文件方法并不感到失望)。

I use a slightly modified version of the “if DEBUG” style of settings that Harper Shelby posted. Obviously depending on the environment (win/linux/etc.) the code might need to be tweaked a bit.

I was in the past using the “if DEBUG” but I found that occasionally I needed to do testing with DEUBG set to False. What I really wanted to distinguish if the environment was production or development, which gave me the freedom to choose the DEBUG level.

PRODUCTION_SERVERS = ['WEBSERVER1','WEBSERVER2',]
if os.environ['COMPUTERNAME'] in PRODUCTION_SERVERS:
    PRODUCTION = True
else:
    PRODUCTION = False

DEBUG = not PRODUCTION
TEMPLATE_DEBUG = DEBUG

# ...

if PRODUCTION:
    DATABASE_HOST = '192.168.1.1'
else:
    DATABASE_HOST = 'localhost'

I’d still consider this way of settings a work in progress. I haven’t seen any one way to handling Django settings that covered all the bases and at the same time wasn’t a total hassle to setup (I’m not down with the 5x settings files methods).


回答 4

我使用settings_local.py和settings_production.py。尝试了几个选项之后,我发现,简单地拥有两个设置文件就容易又快速,很容易在复杂的解决方案上浪费时间。

当对您的Django项目使用mod_python / mod_wsgi时,需要将其指向您的设置文件。如果将其指向本地服务器上的app / settings_local.py和生产服务器上的app / settings_production.py,那么生活会变得很轻松。只需编辑适当的设置文件并重新启动服务器(Django开发服务器将自动重新启动)。

I use a settings_local.py and a settings_production.py. After trying several options I’ve found that it’s easy to waste time with complex solutions when simply having two settings files feels easy and fast.

When you use mod_python/mod_wsgi for your Django project you need to point it to your settings file. If you point it to app/settings_local.py on your local server and app/settings_production.py on your production server then life becomes easy. Just edit the appropriate settings file and restart the server (Django development server will restart automatically).


回答 5

TL; DR:诀窍是os.environment在导入settings/base.py任何文件之前进行修改settings/<purpose>.py,这将大大简化事情。


仅考虑所有这些相互交织的文件,就让我头疼。合并,导入(有时是有条件的),覆盖,修补已设置的内容,以防DEBUG稍后设置更改。什么样的恶梦!

这些年来,我经历了所有不同的解决方案。它们都有些起作用,但是管理起来非常痛苦。WTF!我们真的需要所有麻烦吗?我们从一个settings.py文件开始。现在,我们需要一个文档来将所有这些以正确的顺序正确地组合在一起!

我希望我最终能通过以下解决方法达到(我的)最佳解决方案。

让我们回顾一下目标(一些普通的,一些我的)

  1. 保守秘密-不要将其存储在仓库中!

  2. 通过环境设置(12因子样式)设置/读取密钥和机密。

  3. 具有合理的后备默认设置。理想情况下,对于本地开发,除默认值外,您不需要任何其他内容。

  4. …但请尝试确保默认生产的安全。最好不要在本地错过设置替代,而不必记住要调整默认设置以确保生产安全。

  5. 能够以DEBUG可能影响其他设置的方式打开/关闭(例如,是否使用javascript压缩)。

  6. 在目标设置(例如本地/测试/过渡/生产)之间切换时,仅应基于,而仅此DJANGO_SETTINGS_MODULE而已。

  7. …但是可以通过环境设置(例如)进行进一步的参数设置DATABASE_URL

  8. …还允许他们使用不同的用途设置,并在本地并排运行它们,例如 在本地开发人员机器上进行生产设置,以访问生产数据库或对压缩样式表进行烟雾测试。

  9. 如果未明确设置环境变量(至少需要一个空值),则失败,例如在生产中尤其如此。EMAIL_HOST_PASSWORD

  10. DJANGO_SETTINGS_MODULEdjango-admin startproject期间响应manage.py中的默认设置

  11. 条件语句保持到最低限度,如果条件旨意环境类型(例如,用于生产一系列的日志文件,它的自转),在相关的旨意设置文件中的设置优先。

  1. 不要让django从文件中读取DJANGO_SETTINGS_MODULE设置。
    啊! 想想这是元数据。如果您需要一个文件(例如docker env),请在启动django进程之前将其读入环境。

  2. 不要在您的项目/应用代码中覆盖DJANGO_SETTINGS_MODULE,例如。基于主机名或进程名。
    如果您懒于设置环境变量(例如setup.py test),请在运行项目代码之前在工具中进行设置。

  3. 避免使用django读取其设置的魔术和修补程序,对设置进行预处理,但之后不要干预。

  4. 没有复杂的基于逻辑的废话。配置应该是固定的,不能实时计算。提供备用默认值仅是这里的逻辑。
    您是否真的要调试,为什么要在本地设置正确的设置集,而在远程服务器上的生产环境中(在一台一百台计算机上)计算的结果却有所不同?哦! 单元测试?进行设定?认真吗

我的策略是由优秀的Django的ENVIRON与使用的ini样式文件,提供os.environment当地发展的默认设置,一些最起码的,短settings/<purpose>.py的是有一个文件 import settings/base.py os.environment是从一个设置INI文件。这有效地为我们提供了一种设置注入。

这里的窍门是os.environment在导入之前进行修改settings/base.py

要查看完整的示例,请执行回购:https : //github.com/wooyek/django-settings-strategy

.
   manage.py
├───data
└───website
    ├───settings
          __init__.py   <-- imports local for compatibility
          base.py       <-- almost all the settings, reads from proces environment 
          local.py      <-- a few modifications for local development
          production.py <-- ideally is empty and everything is in base 
          testing.py    <-- mimics production with a reasonable exeptions
          .env          <-- for local use, not kept in repo
       __init__.py
       urls.py
       wsgi.py

设置/.env

本地开发的默认设置。一个秘密文件,主要用于设置所需的环境变量。如果在本地开发中不需要它们,请将它们设置为空值。我们在这里提供默认值,settings/base.py如果环境中缺少其他默认值,则不会在其他任何机器上失败。

设置/ local.py

这里发生的是从中加载环境settings/.env,然后从中导入通用设置settings/base.py。之后,我们可以覆盖一些以简化本地开发。

import logging
import environ

logging.debug("Settings loading: %s" % __file__)

# This will read missing environment variables from a file
# We wan to do this before loading a base settings as they may depend on environment
environ.Env.read_env(DEBUG='True')

from .base import *

ALLOWED_HOSTS += [
    '127.0.0.1',
    'localhost',
    '.example.com',
    'vagrant',
    ]

# https://docs.djangoproject.com/en/1.6/topics/email/#console-backend
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'

LOGGING['handlers']['mail_admins']['email_backend'] = 'django.core.mail.backends.dummy.EmailBackend'

# Sync task testing
# http://docs.celeryproject.org/en/2.5/configuration.html?highlight=celery_always_eager#celery-always-eager

CELERY_ALWAYS_EAGER = True
CELERY_EAGER_PROPAGATES_EXCEPTIONS = True

settings / production.py

对于生产环境,我们不应该期望环境文件,但是如果我们正在测试某些东西,那么拥有一个环境文件会更容易。但是无论如何,免得内联提供很少的默认值,因此settings/base.py可以做出相应的响应。

environ.Env.read_env(Path(__file__) / "production.env", DEBUG='False', ASSETS_DEBUG='False')
from .base import *

这里的主要关注点是DEBUGASSETS_DEBUG覆盖,os.environ仅当它们从环境和文件中丢失时,它们才会应用于python 。

这些将是我们的生产默认值,无需将它们放在环境或文件中,但是如果需要可以覆盖它们。整齐!

设置/ base.py

这些是您最常用的django设置,有一些条件和很多从环境中读取它们的条件。几乎所有内容都在这里,使所有目标环境保持一致并尽可能相似。

主要区别如下(我希望这些是自我解释):

import environ

# https://github.com/joke2k/django-environ
env = environ.Env()

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

# Where BASE_DIR is a django source root, ROOT_DIR is a whole project root
# It may differ BASE_DIR for eg. when your django project code is in `src` folder
# This may help to separate python modules and *django apps* from other stuff
# like documentation, fixtures, docker settings
ROOT_DIR = BASE_DIR

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env('SECRET_KEY')

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env('DEBUG', default=False)

INTERNAL_IPS = [
    '127.0.0.1',
]

ALLOWED_HOSTS = []

if 'ALLOWED_HOSTS' in os.environ:
    hosts = os.environ['ALLOWED_HOSTS'].split(" ")
    BASE_URL = "https://" + hosts[0]
    for host in hosts:
        host = host.strip()
        if host:
            ALLOWED_HOSTS.append(host)

SECURE_SSL_REDIRECT = env.bool('SECURE_SSL_REDIRECT', default=False)

# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

if "DATABASE_URL" in os.environ:  # pragma: no cover
    # Enable database config through environment
    DATABASES = {
        # Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ
        'default': env.db(),
    }

    # Make sure we use have all settings we need
    # DATABASES['default']['ENGINE'] = 'django.contrib.gis.db.backends.postgis'
    DATABASES['default']['TEST'] = {'NAME': os.environ.get("DATABASE_TEST_NAME", None)}
    DATABASES['default']['OPTIONS'] = {
        'options': '-c search_path=gis,public,pg_catalog',
        'sslmode': 'require',
    }
else:
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            # 'ENGINE': 'django.contrib.gis.db.backends.spatialite',
            'NAME': os.path.join(ROOT_DIR, 'data', 'db.dev.sqlite3'),
            'TEST': {
                'NAME': os.path.join(ROOT_DIR, 'data', 'db.test.sqlite3'),
            }
        }
    }

STATIC_ROOT = os.path.join(ROOT_DIR, 'static')

# django-assets
# http://django-assets.readthedocs.org/en/latest/settings.html

ASSETS_LOAD_PATH = STATIC_ROOT
ASSETS_ROOT = os.path.join(ROOT_DIR, 'assets', "compressed")
ASSETS_DEBUG = env('ASSETS_DEBUG', default=DEBUG)  # Disable when testing compressed file in DEBUG mode
if ASSETS_DEBUG:
    ASSETS_URL = STATIC_URL
    ASSETS_MANIFEST = "json:{}".format(os.path.join(ASSETS_ROOT, "manifest.json"))
else:
    ASSETS_URL = STATIC_URL + "assets/compressed/"
    ASSETS_MANIFEST = "json:{}".format(os.path.join(STATIC_ROOT, 'assets', "compressed", "manifest.json"))
ASSETS_AUTO_BUILD = ASSETS_DEBUG
ASSETS_MODULES = ('website.assets',)

最后一位显示此处的功率。ASSETS_DEBUG有一个合理的默认值,可以将其覆盖settings/production.py,甚至可以由环境设置覆盖!好极了!

实际上,我们具有不同的重要性等级:

  1. settings / .py-根据目的设置默认值,不存储秘密
  2. settings / base.py-主要由环境控制
  3. 过程环境设置-12要素宝贝!
  4. settings / .env-本地默认设置,易于启动

TL;DR: The trick is to modify os.environment before you import settings/base.py in any settings/<purpose>.py, this will greatly simplify things.


Just thinking about all these intertwining files gives me a headache. Combining, importing (sometimes conditionally), overriding, patching of what was already set in case DEBUG setting changed later on. What a nightmare!

Through the years I went through all different solutions. They all somewhat work, but are so painful to manage. WTF! Do we really need all that hassle? We started with just one settings.py file. Now we need a documentation just to correctly combine all these together in a correct order!

I hope I finally hit the (my) sweet spot with the solution below.

Let’s recap the goals (some common, some mine)

  1. Keep secrets a secret — don’t store them in a repo!

  2. Set/read keys and secrets through environment settings, 12 factor style.

  3. Have sensible fallback defaults. Ideally for local development you don’t need anything more beside defaults.

  4. …but try to keep defaults production safe. It’s better to miss a setting override locally, than having to remember to adjust default settings safe for production.

  5. Have the ability to switch DEBUG on/off in a way that can have an effect on other settings (eg. using javascript compressed or not).

  6. Switching between purpose settings, like local/testing/staging/production, should be based only on DJANGO_SETTINGS_MODULE, nothing more.

  7. …but allow further parameterization through environment settings like DATABASE_URL.

  8. …also allow them to use different purpose settings and run them locally side by side, eg. production setup on local developer machine, to access production database or smoke test compressed style sheets.

  9. Fail if an environment variable is not explicitly set (requiring an empty value at minimum), especially in production, eg. EMAIL_HOST_PASSWORD.

  10. Respond to default DJANGO_SETTINGS_MODULE set in manage.py during django-admin startproject

  11. Keep conditionals to a minimum, if the condition is the purposed environment type (eg. for production set log file and it’s rotation), override settings in associated purposed settings file.

Do not’s

  1. Do not let django read DJANGO_SETTINGS_MODULE setting form a file.
    Ugh! Think of how meta this is. If you need to have a file (like docker env) read that into the environment before staring up a django process.

  2. Do not override DJANGO_SETTINGS_MODULE in your project/app code, eg. based on hostname or process name.
    If you are lazy to set environment variable (like for setup.py test) do it in tooling just before you run your project code.

  3. Avoid magic and patching of how django reads it’s settings, preprocess the settings but do not interfere afterwards.

  4. No complicated logic based nonsense. Configuration should be fixed and materialized not computed on the fly. Providing a fallback defaults is just enough logic here.
    Do you really want to debug, why locally you have correct set of settings but in production on a remote server, on one of hundred machines, something computed differently? Oh! Unit tests? For settings? Seriously?

Solution

My strategy consists of excellent django-environ used with ini style files, providing os.environment defaults for local development, some minimal and short settings/<purpose>.py files that have an import settings/base.py AFTER the os.environment was set from an INI file. This effectively give us a kind of settings injection.

The trick here is to modify os.environment before you import settings/base.py.

To see the full example go do the repo: https://github.com/wooyek/django-settings-strategy

.
│   manage.py
├───data
└───website
    ├───settings
    │   │   __init__.py   <-- imports local for compatibility
    │   │   base.py       <-- almost all the settings, reads from proces environment 
    │   │   local.py      <-- a few modifications for local development
    │   │   production.py <-- ideally is empty and everything is in base 
    │   │   testing.py    <-- mimics production with a reasonable exeptions
    │   │   .env          <-- for local use, not kept in repo
    │   __init__.py
    │   urls.py
    │   wsgi.py

settings/.env

A defaults for local development. A secret file, to mostly set required environment variables. Set them to empty values if they are not required in local development. We provide defaults here and not in settings/base.py to fail on any other machine if the’re missing from the environment.

settings/local.py

What happens in here, is loading environment from settings/.env, then importing common settings from settings/base.py. After that we can override a few to ease local development.

import logging
import environ

logging.debug("Settings loading: %s" % __file__)

# This will read missing environment variables from a file
# We wan to do this before loading a base settings as they may depend on environment
environ.Env.read_env(DEBUG='True')

from .base import *

ALLOWED_HOSTS += [
    '127.0.0.1',
    'localhost',
    '.example.com',
    'vagrant',
    ]

# https://docs.djangoproject.com/en/1.6/topics/email/#console-backend
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'

LOGGING['handlers']['mail_admins']['email_backend'] = 'django.core.mail.backends.dummy.EmailBackend'

# Sync task testing
# http://docs.celeryproject.org/en/2.5/configuration.html?highlight=celery_always_eager#celery-always-eager

CELERY_ALWAYS_EAGER = True
CELERY_EAGER_PROPAGATES_EXCEPTIONS = True

settings/production.py

For production we should not expect an environment file, but it’s easier to have one if we’re testing something. But anyway, lest’s provide few defaults inline, so settings/base.py can respond accordingly.

environ.Env.read_env(Path(__file__) / "production.env", DEBUG='False', ASSETS_DEBUG='False')
from .base import *

The main point of interest here are DEBUG and ASSETS_DEBUG overrides, they will be applied to the python os.environ ONLY if they are MISSING from the environment and the file.

These will be our production defaults, no need to put them in the environment or file, but they can be overridden if needed. Neat!

settings/base.py

These are your mostly vanilla django settings, with a few conditionals and lot’s of reading them from the environment. Almost everything is in here, keeping all the purposed environments consistent and as similar as possible.

The main differences are below (I hope these are self explanatory):

import environ

# https://github.com/joke2k/django-environ
env = environ.Env()

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

# Where BASE_DIR is a django source root, ROOT_DIR is a whole project root
# It may differ BASE_DIR for eg. when your django project code is in `src` folder
# This may help to separate python modules and *django apps* from other stuff
# like documentation, fixtures, docker settings
ROOT_DIR = BASE_DIR

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env('SECRET_KEY')

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env('DEBUG', default=False)

INTERNAL_IPS = [
    '127.0.0.1',
]

ALLOWED_HOSTS = []

if 'ALLOWED_HOSTS' in os.environ:
    hosts = os.environ['ALLOWED_HOSTS'].split(" ")
    BASE_URL = "https://" + hosts[0]
    for host in hosts:
        host = host.strip()
        if host:
            ALLOWED_HOSTS.append(host)

SECURE_SSL_REDIRECT = env.bool('SECURE_SSL_REDIRECT', default=False)

# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

if "DATABASE_URL" in os.environ:  # pragma: no cover
    # Enable database config through environment
    DATABASES = {
        # Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ
        'default': env.db(),
    }

    # Make sure we use have all settings we need
    # DATABASES['default']['ENGINE'] = 'django.contrib.gis.db.backends.postgis'
    DATABASES['default']['TEST'] = {'NAME': os.environ.get("DATABASE_TEST_NAME", None)}
    DATABASES['default']['OPTIONS'] = {
        'options': '-c search_path=gis,public,pg_catalog',
        'sslmode': 'require',
    }
else:
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            # 'ENGINE': 'django.contrib.gis.db.backends.spatialite',
            'NAME': os.path.join(ROOT_DIR, 'data', 'db.dev.sqlite3'),
            'TEST': {
                'NAME': os.path.join(ROOT_DIR, 'data', 'db.test.sqlite3'),
            }
        }
    }

STATIC_ROOT = os.path.join(ROOT_DIR, 'static')

# django-assets
# http://django-assets.readthedocs.org/en/latest/settings.html

ASSETS_LOAD_PATH = STATIC_ROOT
ASSETS_ROOT = os.path.join(ROOT_DIR, 'assets', "compressed")
ASSETS_DEBUG = env('ASSETS_DEBUG', default=DEBUG)  # Disable when testing compressed file in DEBUG mode
if ASSETS_DEBUG:
    ASSETS_URL = STATIC_URL
    ASSETS_MANIFEST = "json:{}".format(os.path.join(ASSETS_ROOT, "manifest.json"))
else:
    ASSETS_URL = STATIC_URL + "assets/compressed/"
    ASSETS_MANIFEST = "json:{}".format(os.path.join(STATIC_ROOT, 'assets', "compressed", "manifest.json"))
ASSETS_AUTO_BUILD = ASSETS_DEBUG
ASSETS_MODULES = ('website.assets',)

The last bit shows the power here. ASSETS_DEBUG has a sensible default, which can be overridden in settings/production.py and even that that can be overridden by an environment setting! Yay!

In effect we have a mixed hierarchy of importance:

  1. settings/.py – sets defaults based on purpose, does not store secrets
  2. settings/base.py – is mostly controlled by environment
  3. process environment settings – 12 factor baby!
  4. settings/.env – local defaults for easy startup

回答 6

我在django-split-settings的帮助下管理我的配置。

它是默认设置的替代品。它很简单,但可配置。并且不需要重构您的现有设置。

这是一个小示例(文件example/settings/__init__.py):

from split_settings.tools import optional, include
import os

if os.environ['DJANGO_SETTINGS_MODULE'] == 'example.settings':
    include(
        'components/default.py',
        'components/database.py',
        # This file may be missing:
        optional('local_settings.py'),

        scope=globals()
    )

而已。

更新资料

我写了一篇博客文章,介绍如何使用来管理django设置django-split-sttings。看一看!

I manage my configurations with the help of django-split-settings.

It is a drop-in replacement for the default settings. It is simple, yet configurable. And refactoring of your exisitng settings is not required.

Here’s a small example (file example/settings/__init__.py):

from split_settings.tools import optional, include
import os

if os.environ['DJANGO_SETTINGS_MODULE'] == 'example.settings':
    include(
        'components/default.py',
        'components/database.py',
        # This file may be missing:
        optional('local_settings.py'),

        scope=globals()
    )

That’s it.

Update

I wrote a blog post about managing django‘s settings with django-split-sttings. Have a look!


回答 7

这些解决方案中的大多数问题是您在本地设置之前之后应用了本地设置。

因此,不可能覆盖诸如

  • 特定于环境的设置定义了内存缓存池的地址,在主设置文件中,此值用于配置缓存后端
  • 特定于环境的设置将应用程序/中间件添加或删除为默认设置

与此同时。

可以使用带有ConfigParser类的“ ini”样式的配置文件来实现一种解决方案。它支持多个文件,惰性字符串插值,默认值和许多其他功能。一旦加载了许多文件,就可以加载更多文件,并且它们的值将覆盖先前的文件(如果有)。

您加载一个或多个配置文件,具体取决于机器地址,环境变量甚至以前加载的配置文件中的值。然后,您只需使用解析后的值来填充设置。

我成功使用的一种策略是:

  • 加载默认defaults.ini文件
  • 检查机器名称,并加载与反向FQDN匹配的所有文件,从最短匹配到最长匹配(因此,我先加载net.ini,然后加载,然后net.domain.ini再加载net.domain.webserver01.ini,每个可能覆盖前一个值)。此帐户也用于开发人员的计算机,因此每个人都可以设置其首选的数据库驱动程序等以进行本地开发
  • 检查是否声明了“集群名称”,在这种情况下cluster.cluster_name.ini,请检查load ,它可以定义数据库和缓存IP之类的内容

作为可以实现此目标的示例,您可以为每个环境定义一个“子域”值,然后在默认设置(如hostname: %(subdomain).whatever.net)中使用它来定义django需要工作的所有必需的主机名和cookie。

这是我可以得到的DRY,大多数(现有)文件只有3或4个设置。最重要的是,我必须管理客户配置,因此存在另外一组配置文件(带有数据库名称,用户和密码,分配的子域等),每个客户一个或多个。

可以根据需要将其缩放为低或高,您只需将要在每个环境中配置的密钥放入配置文件中,然后在需要新配置时,将先前的值放入默认配置中,然后覆盖它即可。在必要时。

该系统已经证明是可靠的,并且可以与版本控制一起很好地工作。它已长期用于管理两个单独的应用程序集群(每台机器15个或更多django站点的单独实例),拥有50多个客户,这些集群根据sysadmin的心情来改变大小和成员。 。

The problem with most of these solutions is that you either have your local settings applied before the common ones, or after them.

So it’s impossible to override things like

  • the env-specific settings define the addresses for the memcached pool, and in the main settings file this value is used to configure the cache backend
  • the env-specific settings add or remove apps/middleware to the default one

at the same time.

One solution can be implemented using “ini”-style config files with the ConfigParser class. It supports multiple files, lazy string interpolation, default values and a lot of other goodies. Once a number of files have been loaded, more files can be loaded and their values will override the previous ones, if any.

You load one or more config files, depending on the machine address, environment variables and even values in previously loaded config files. Then you just use the parsed values to populate the settings.

One strategy I have successfully used has been:

  • Load a default defaults.ini file
  • Check the machine name, and load all files which matched the reversed FQDN, from the shortest match to the longest match (so, I loaded net.ini, then net.domain.ini, then net.domain.webserver01.ini, each one possibly overriding values of the previous). This account also for developers’ machines, so each one could set up its preferred database driver, etc. for local development
  • Check if there is a “cluster name” declared, and in that case load cluster.cluster_name.ini, which can define things like database and cache IPs

As an example of something you can achieve with this, you can define a “subdomain” value per-env, which is then used in the default settings (as hostname: %(subdomain).whatever.net) to define all the necessary hostnames and cookie things django needs to work.

This is as DRY I could get, most (existing) files had just 3 or 4 settings. On top of this I had to manage customer configuration, so an additional set of configuration files (with things like database names, users and passwords, assigned subdomain etc) existed, one or more per customer.

One can scale this as low or as high as necessary, you just put in the config file the keys you want to configure per-environment, and once there’s need for a new config, put the previous value in the default config, and override it where necessary.

This system has proven reliable and works well with version control. It has been used for long time managing two separate clusters of applications (15 or more separate instances of the django site per machine), with more than 50 customers, where the clusters were changing size and members depending on the mood of the sysadmin…


回答 8

我也在与Laravel合作,我喜欢在那里的实现。我试图模仿它,并将其与T.Stone提出的解决方案结合起来(见上):

PRODUCTION_SERVERS = ['*.webfaction.com','*.whatever.com',]

def check_env():
    for item in PRODUCTION_SERVERS:
        match = re.match(r"(^." + item + "$)", socket.gethostname())
        if match:
            return True

if check_env():
    PRODUCTION = True
else:
    PRODUCTION = False

DEBUG = not PRODUCTION

也许这样的事情会帮助您。

I am also working with Laravel and I like the implementation there. I tried to mimic it and combining it with the solution proposed by T. Stone (look above):

PRODUCTION_SERVERS = ['*.webfaction.com','*.whatever.com',]

def check_env():
    for item in PRODUCTION_SERVERS:
        match = re.match(r"(^." + item + "$)", socket.gethostname())
        if match:
            return True

if check_env():
    PRODUCTION = True
else:
    PRODUCTION = False

DEBUG = not PRODUCTION

Maybe something like this would help you.


回答 9

请记住,settings.py是实时代码文件。假设您没有在生产环境中设置DEBUG(这是最佳做法),则可以执行以下操作:

if DEBUG:
    STATIC_PATH = /path/to/dev/files
else:
    STATIC_PATH = /path/to/production/files

这很基本,但是从理论上讲,您可以仅根据DEBUG的值-或要使用的任何其他变量或代码检查,将复杂性提高到任何水平。

Remember that settings.py is a live code file. Assuming that you don’t have DEBUG set on production (which is a best practice), you can do something like:

if DEBUG:
    STATIC_PATH = /path/to/dev/files
else:
    STATIC_PATH = /path/to/production/files

Pretty basic, but you could, in theory, go up to any level of complexity based on just the value of DEBUG – or any other variable or code check you wanted to use.


回答 10

对于我的大多数项目,我使用以下模式:

  1. 在我存储所有环境通用设置的位置创建settings_base.py
  2. 每当需要使用具有特定要求的新环境时,我都会创建一个新的设置文件(例如settings_local.py),该文件将继承settings_base.py的内容并覆盖/添加适当的设置变量(from settings_base import *

(要使用自定义设置运行manage.py文件只需使用–settings命令选项:manage.py <command> --settings=settings_you_wish_to_use.py

For most of my projects I use following pattern:

  1. Create settings_base.py where I store settings that are common for all environments
  2. Whenever I need to use new environment with specific requirements I create new settings file (eg. settings_local.py) which inherits contents of settings_base.py and overrides/adds proper settings variables (from settings_base import *)

(To run manage.py with custom settings file you simply use –settings command option: manage.py <command> --settings=settings_you_wish_to_use.py)


回答 11

我对这个问题的解决方案在某种程度上也是这里已经提到的一些解决方案的混合:

  • 我保留了一个名为dev和prod中local_settings.py内容的文件USING_LOCAL = TrueUSING_LOCAL = False
  • settings.py我对该文件进行导入以获取USING_LOCAL设置

然后,我将所有与环境相关的设置都基于该设置:

DEBUG = USING_LOCAL
if USING_LOCAL:
    # dev database settings
else:
    # prod database settings

我宁愿拥有两个需要维护的单独的settings.py文件,因为与将它们分散在多个文件中相比,可以将设置结构化为一个文件。这样,当我更新设置时,我不会忘记在两种环境下都进行设置。

当然,每种方法都有其缺点,这一方法也不exceptions。这里的问题是,local_settings.py每当我将更改推送到生产环境时,我都无法覆盖文件,这意味着我不能盲目地复制所有文件,但这是我可以忍受的。

My solution to that problem is also somewhat of a mix of some solutions already stated here:

  • I keep a file called local_settings.py that has the content USING_LOCAL = True in dev and USING_LOCAL = False in prod
  • In settings.py I do an import on that file to get the USING_LOCAL setting

I then base all my environment-dependent settings on that one:

DEBUG = USING_LOCAL
if USING_LOCAL:
    # dev database settings
else:
    # prod database settings

I prefer this to having two separate settings.py files that I need to maintain as I can keep my settings structured in a single file easier than having them spread across several files. Like this, when I update a setting I don’t forget to do it for both environments.

Of course that every method has its disadvantages and this one is no exception. The problem here is that I can’t overwrite the local_settings.py file whenever I push my changes into production, meaning I can’t just copy all files blindly, but that’s something I can live with.


回答 12

我使用了上面提到的jpartogi的一种变体,发现它略短一些:

import platform
from django.core.management import execute_manager 

computername = platform.node()

try:
  settings = __import__(computername + '_settings')
except ImportError: 
  import sys
  sys.stderr.write("Error: Can't find the file '%r_settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file local_settings.py does indeed exist, it's causing an ImportError somehow.)\n" % (computername, __file__))
  sys.exit(1)

if __name__ == "__main__":
  execute_manager(settings)

基本上在每台计算机(开发或生产)上,我都有适当的hostname_settings.py文件,该文件会动态加载。

I use a variation of what jpartogi mentioned above, that I find a little shorter:

import platform
from django.core.management import execute_manager 

computername = platform.node()

try:
  settings = __import__(computername + '_settings')
except ImportError: 
  import sys
  sys.stderr.write("Error: Can't find the file '%r_settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file local_settings.py does indeed exist, it's causing an ImportError somehow.)\n" % (computername, __file__))
  sys.exit(1)

if __name__ == "__main__":
  execute_manager(settings)

Basically on each computer (development or production) I have the appropriate hostname_settings.py file that gets dynamically loaded.


回答 13

也有Django Classy Settings。我个人是它的忠实拥护者。它是由Django IRC上最活跃的人之一构建的。您将使用环境变量进行设置。

http://django-classy-settings.readthedocs.io/en/latest/

There is also Django Classy Settings. I personally am a big fan of it. It’s built by one of the most active people on the Django IRC. You would use environment vars to set things.

http://django-classy-settings.readthedocs.io/en/latest/


回答 14

1-在您的应用程序内创建一个新文件夹,并为其命名设置。

2-现在__init__.py在其中创建一个新文件,并在其中写入

from .base import *

try:
    from .local import *
except:
    pass

try:
    from .production import *
except:
    pass

3 -创建在设置三个新文件夹的名称local.pyproduction.pybase.py

4-在内部base.py,复制上一个settings.py文件夹的所有内容,并用其他不同的名称重命名old_settings.py

5-在base.py中,更改BASE_DIR路径以指向新的设置路径

旧路径-> BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

新路径-> BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

这样,可以在生产和本地开发之间构建项目目录并对其进行管理。

1 – Create a new folder inside your app and name settings to it.

2 – Now create a new __init__.py file in it and inside it write

from .base import *

try:
    from .local import *
except:
    pass

try:
    from .production import *
except:
    pass

3 – Create three new files in the settings folder name local.py and production.py and base.py.

4 – Inside base.py, copy all the content of previous settings.py folder and rename it with something different, let’s say old_settings.py.

5 – In base.py change your BASE_DIR path to point to your new path of setting

Old path-> BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

New path -> BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

This way, the project dir can be structured and can be manageable among production and local development.


回答 15

为了settings在不同的环境上使用不同的配置,请创建不同的设置文件。然后,在部署脚本中,使用--settings=<my-settings.py>参数启动服务器,通过该参数可以在不同的环境上使用不同的设置

使用这种方法的好处

  1. 您的设置将根据每个环境进行模块化

  2. 您可以在中导入master_settings.py包含基本配置的内容,environmnet_configuration.py并覆盖要在该环境中更改的值。

  3. 如果您有庞大的团队,则每个开发人员可能都有自己的团队,可以将其local_settings.py添加到代码存储库中,而无需修改服务器配置。您可以添加这些本地设置,.gitnore如果您使用的git或者.hginore,如果你的Mercurial版本控制(或任何其他)。这样,本地设置甚至不会成为保持干净的实际代码库的一部分。

In order to use different settings configuration on different environment, create different settings file. And in your deployment script, start the server using --settings=<my-settings.py> parameter, via which you can use different settings on different environment.

Benefits of using this approach:

  1. Your settings will be modular based on each environment

  2. You may import the master_settings.py containing the base configuration in the environmnet_configuration.py and override the values that you want to change in that environment.

  3. If you have huge team, each developer may have their own local_settings.py which they can add to the code repository without any risk of modifying the server configuration. You can add these local settings to .gitnore if you use git or .hginore if you Mercurial for Version Control (or any other). That way local settings won’t even be the part of actual code base keeping it clean.


回答 16

我的设置如下拆分

settings/
     |
     |- base.py
     |- dev.py
     |- prod.py  

我们有3个环境

  • 开发者
  • 分期
  • 生产

现在显然,登台和生产应该具有最大可能的相似环境。所以我们都坚持prod.py

但是在某些情况下,我不得不确定正在运行的服务器是生产服务器。@T。斯通的答案帮助我写了以下支票。

from socket import gethostname, gethostbyname  
PROD_HOSTS = ["webserver1", "webserver2"]

DEBUG = False
ALLOWED_HOSTS = [gethostname(), gethostbyname(gethostname()),]


if any(host in PROD_HOSTS for host in ALLOWED_HOSTS):
    SESSION_COOKIE_SECURE = True
    CSRF_COOKIE_SECURE = True  

I had my settings split as follows

settings/
     |
     |- base.py
     |- dev.py
     |- prod.py  

We have 3 environments

  • dev
  • staging
  • production

Now obviously staging and production should have the maximum possible similar environment. So we kept prod.py for both.

But there was a case where I had to identify running server is a production server. @T. Stone ‘s answer helped me write check as follows.

from socket import gethostname, gethostbyname  
PROD_HOSTS = ["webserver1", "webserver2"]

DEBUG = False
ALLOWED_HOSTS = [gethostname(), gethostbyname(gethostname()),]


if any(host in PROD_HOSTS for host in ALLOWED_HOSTS):
    SESSION_COOKIE_SECURE = True
    CSRF_COOKIE_SECURE = True  

回答 17

我在manage.py中对其进行区分,并创建了两个单独的设置文件:local_settings.py和prod_settings.py。

在manage.py中,我检查服务器是本地服务器还是生产服务器。如果是本地服务器,则将加载local_settings.py,如果是生产服务器,则将加载prod_settings.py。基本上是这样的:

#!/usr/bin/env python
import sys
import socket
from django.core.management import execute_manager 

ipaddress = socket.gethostbyname( socket.gethostname() )
if ipaddress == '127.0.0.1':
    try:
        import local_settings # Assumed to be in the same directory.
        settings = local_settings
    except ImportError:
        import sys
        sys.stderr.write("Error: Can't find the file 'local_settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file local_settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
        sys.exit(1)
else:
    try:
        import prod_settings # Assumed to be in the same directory.
        settings = prod_settings    
    except ImportError:
        import sys
        sys.stderr.write("Error: Can't find the file 'prod_settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file prod_settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
        sys.exit(1)

if __name__ == "__main__":
    execute_manager(settings)

我发现将设置文件分为两个单独的文件比在设置文件中进行大量的ifs更为容易。

I differentiate it in manage.py and created two separate settings file: local_settings.py and prod_settings.py.

In manage.py I check whether the server is local server or production server. If it is a local server it would load up local_settings.py and it is a production server it would load up prod_settings.py. Basically this is how it would look like:

#!/usr/bin/env python
import sys
import socket
from django.core.management import execute_manager 

ipaddress = socket.gethostbyname( socket.gethostname() )
if ipaddress == '127.0.0.1':
    try:
        import local_settings # Assumed to be in the same directory.
        settings = local_settings
    except ImportError:
        import sys
        sys.stderr.write("Error: Can't find the file 'local_settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file local_settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
        sys.exit(1)
else:
    try:
        import prod_settings # Assumed to be in the same directory.
        settings = prod_settings    
    except ImportError:
        import sys
        sys.stderr.write("Error: Can't find the file 'prod_settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file prod_settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
        sys.exit(1)

if __name__ == "__main__":
    execute_manager(settings)

I found it to be easier to separate the settings file into two separate file instead of doing lots of ifs inside the settings file.


回答 18

如果愿意,也可以选择维护其他文件:如果使用git或任何其他VCS将代码从本地推送到服务器,则可以将设置文件添加到.gitignore。

这样您就可以在两个地方都拥有不同的内容,而不会出现任何问题。因此,在服务器上,您可以配置独立版本的settings.py,对本地所做的任何更改都不会反映在服务器上,反之亦然。

另外,它还会从github上删除settings.py文件,这是一个很大的错误,我看到很多新手都在这样做。

As an alternative to maintain different file if you wiil: If you are using git or any other VCS to push codes from local to server, what you can do is add the settings file to .gitignore.

This will allow you to have different content in both places without any problem. SO on server you can configure an independent version of settings.py and any changes made on the local wont reflect on server and vice versa.

In addition, it will remove the settings.py file from github also, the big fault, which i have seen many newbies doing.


回答 19

制作settings.py的多个版本是12 Factor App方法的反模式。使用python-decoupledjango-environ代替。

Making multiple versions of settings.py is an anti pattern for 12 Factor App methodology. use python-decouple or django-environ instead.


回答 20

我认为最好的解决方案是@T建议的。斯通,但我不知道为什么不在Django中使用DEBUG标志。我为我的网站编写以下代码:

if DEBUG:
    from .local_settings import *

总是简单的解决方案比复杂的解决方案好。

I think the best solution is suggested by @T. Stone, but I don’t know why just don’t use the DEBUG flag in Django. I Write the below code for my website:

if DEBUG:
    from .local_settings import *

Always the simple solutions are better than complex ones.


回答 21

我发现这里的回复非常有帮助。(是否已更明确地解决了此问题?上次答复是一年前。)在考虑了列出的所有方法之后,我想出了一个我未在此处列出的解决方案。

我的标准是:

  • 一切都应该在源代码控制中。我不喜欢随便摆姿势。
  • 理想情况下,将设置保存在一个文件中。如果我没看对的话我会忘记的:)
  • 无需手动编辑即可部署。应该能够使用单个结构命令进行测试/推送/部署。
  • 避免将开发设置泄漏到生产中。
  • 保持尽可能接近“标准”(*咳嗽*)Django布局。

我以为打开主机是有道理的,但后来发现这里的真正问题是针对不同环境的不同设置,并且花了很多时间。我把这个代码在结束我的settings.py文件中:

try:
    os.environ['DJANGO_DEVELOPMENT_SERVER'] # throws error if unset
    DEBUG = True
    TEMPLATE_DEBUG = True
    # This is naive but possible. Could also redeclare full app set to control ordering. 
    # Note that it requires a list rather than the generated tuple.
    INSTALLED_APPS.extend([
        'debug_toolbar',
        'django_nose',
    ])
    # Production database settings, alternate static/media paths, etc...
except KeyError: 
    print 'DJANGO_DEVELOPMENT_SERVER environment var not set; using production settings'

这样,该应用程序默认为生产设置,这意味着您将开发环境明确“列入白名单”。与在相反的情况下忘记在本地设置环境变量相比,如果忘记在生产环境中设置某些内容并使用某些开发设置,则要安全得多。

从外壳或.bash_profile或任何地方进行本地开发时:

$ export DJANGO_DEVELOPMENT_SERVER=yep

(或者,如果您是在Windows上进行开发,则可以通过“控制面板”或目前称为“控制面板”的工具进行设置。Windows总是使它晦涩难懂,因此您可以设置环境变量。)

使用这种方法,开发人员设置全部集中在一个(标准)位置,并在需要时仅覆盖生产设置。任何与开发设置有关的更改都应该完全安全地进行源代码控制,而不会影响生产。

I found the responses here very helpful. (Has this been more definitively solved? The last response was a year ago.) After considering all the approaches listed, I came up with a solution that I didn’t see listed here.

My criteria were:

  • Everything should be in source control. I don’t like fiddly bits lying around.
  • Ideally, keep settings in one file. I forget things if I’m not looking right at them :)
  • No manual edits to deploy. Should be able to test/push/deploy with a single fabric command.
  • Avoid leaking development settings into production.
  • Keep as close as possible to “standard” (*cough*) Django layout as possible.

I thought switching on the host machine made some sense, but then figured the real issue here is different settings for different environments, and had an aha moment. I put this code at the end of my settings.py file:

try:
    os.environ['DJANGO_DEVELOPMENT_SERVER'] # throws error if unset
    DEBUG = True
    TEMPLATE_DEBUG = True
    # This is naive but possible. Could also redeclare full app set to control ordering. 
    # Note that it requires a list rather than the generated tuple.
    INSTALLED_APPS.extend([
        'debug_toolbar',
        'django_nose',
    ])
    # Production database settings, alternate static/media paths, etc...
except KeyError: 
    print 'DJANGO_DEVELOPMENT_SERVER environment var not set; using production settings'

This way, the app defaults to production settings, which means you are explicitly “whitelisting” your development environment. It is much safer to forget to set the environment variable locally than if it were the other way around and you forgot to set something in production and let some dev settings be used.

When developing locally, either from the shell or in a .bash_profile or wherever:

$ export DJANGO_DEVELOPMENT_SERVER=yep

(Or if you’re developing on Windows, set via the Control Panel or whatever its called these days… Windows always made it so obscure that you could set environment variables.)

With this approach, the dev settings are all in one (standard) place, and simply override the production ones where needed. Any mucking around with development settings should be completely safe to commit to source control with no impact on production.


Django ModelAdmin中的“ list_display”可以显示ForeignKey字段的属性吗?

问题:Django ModelAdmin中的“ list_display”可以显示ForeignKey字段的属性吗?

我有一个Person模型,它与有一个外键关系Book,该模型有许多字段,但我最关心的是author(标准CharField)。

话虽如此,在我的PersonAdmin模型中,我想book.author使用显示list_display

class PersonAdmin(admin.ModelAdmin):
    list_display = ['book.author',]

我已经尝试了所有显而易见的方法来执行此操作,但是似乎没有任何效果。

有什么建议么?

I have a Person model that has a foreign key relationship to Book, which has a number of fields, but I’m most concerned about author (a standard CharField).

With that being said, in my PersonAdmin model, I’d like to display book.author using list_display:

class PersonAdmin(admin.ModelAdmin):
    list_display = ['book.author',]

I’ve tried all of the obvious methods for doing so, but nothing seems to work.

Any suggestions?


回答 0

作为另一种选择,您可以进行如下查找:

class UserAdmin(admin.ModelAdmin):
    list_display = (..., 'get_author')

    def get_author(self, obj):
        return obj.book.author
    get_author.short_description = 'Author'
    get_author.admin_order_field = 'book__author'

As another option, you can do look ups like:

class UserAdmin(admin.ModelAdmin):
    list_display = (..., 'get_author')

    def get_author(self, obj):
        return obj.book.author
    get_author.short_description = 'Author'
    get_author.admin_order_field = 'book__author'

回答 1

尽管上面有很多很棒的答案,但由于我是Django的新手,所以我仍然受困。这是我从一个新手的角度进行的解释。

models.py

class Author(models.Model):
    name = models.CharField(max_length=255)

class Book(models.Model):
    author = models.ForeignKey(Author)
    title = models.CharField(max_length=255)

admin.py(不正确的方式) -您认为使用’model__field’进行引用可以正常工作,但它不起作用

class BookAdmin(admin.ModelAdmin):
    model = Book
    list_display = ['title', 'author__name', ]

admin.site.register(Book, BookAdmin)

admin.py(正确的方式) -这就是您以Django方式引用外键名称的方式

class BookAdmin(admin.ModelAdmin):
    model = Book
    list_display = ['title', 'get_name', ]

    def get_name(self, obj):
        return obj.author.name
    get_name.admin_order_field  = 'author'  #Allows column order sorting
    get_name.short_description = 'Author Name'  #Renames column head

    #Filtering on side - for some reason, this works
    #list_filter = ['title', 'author__name']

admin.site.register(Book, BookAdmin)

有关其他参考,请参见此处的Django模型链接

Despite all the great answers above and due to me being new to Django, I was still stuck. Here’s my explanation from a very newbie perspective.

models.py

class Author(models.Model):
    name = models.CharField(max_length=255)

class Book(models.Model):
    author = models.ForeignKey(Author)
    title = models.CharField(max_length=255)

admin.py (Incorrect Way) – you think it would work by using ‘model__field’ to reference, but it doesn’t

class BookAdmin(admin.ModelAdmin):
    model = Book
    list_display = ['title', 'author__name', ]

admin.site.register(Book, BookAdmin)

admin.py (Correct Way) – this is how you reference a foreign key name the Django way

class BookAdmin(admin.ModelAdmin):
    model = Book
    list_display = ['title', 'get_name', ]

    def get_name(self, obj):
        return obj.author.name
    get_name.admin_order_field  = 'author'  #Allows column order sorting
    get_name.short_description = 'Author Name'  #Renames column head

    #Filtering on side - for some reason, this works
    #list_filter = ['title', 'author__name']

admin.site.register(Book, BookAdmin)

For additional reference, see the Django model link here


回答 2

和其余的一样,我也使用可调用对象。但是它们有一个缺点:默认情况下,您无法订购它们。幸运的是,有一个解决方案:

的Django> = 1.8

def author(self, obj):
    return obj.book.author
author.admin_order_field  = 'book__author'

Django <1.8

def author(self):
    return self.book.author
author.admin_order_field  = 'book__author'

Like the rest, I went with callables too. But they have one downside: by default, you can’t order on them. Fortunately, there is a solution for that:

Django >= 1.8

def author(self, obj):
    return obj.book.author
author.admin_order_field  = 'book__author'

Django < 1.8

def author(self):
    return self.book.author
author.admin_order_field  = 'book__author'

回答 3

请注意,添加该get_author功能会减慢admin中的list_display速度,因为显示每个人都会进行SQL查询。

为了避免这种情况,您需要get_queryset在PersonAdmin中修改方法,例如:

def get_queryset(self, request):
    return super(PersonAdmin,self).get_queryset(request).select_related('book')

之前:36.02毫秒内有73个查询(管理员中有67个重复查询)

之后:10.81毫秒内进行6次查询

Please note that adding the get_author function would slow the list_display in the admin, because showing each person would make a SQL query.

To avoid this, you need to modify get_queryset method in PersonAdmin, for example:

def get_queryset(self, request):
    return super(PersonAdmin,self).get_queryset(request).select_related('book')

Before: 73 queries in 36.02ms (67 duplicated queries in admin)

After: 6 queries in 10.81ms


回答 4

根据文档,您只能显示外键的__unicode__表示形式:

http://docs.djangoproject.com/en/dev/ref/contrib/admin/#list-display

似乎很奇怪,它不支持'book__author'DB API其他地方使用的样式格式。

原来有一张用于此功能的票证,标记为“无法修复”。

According to the documentation, you can only display the __unicode__ representation of a ForeignKey:

http://docs.djangoproject.com/en/dev/ref/contrib/admin/#list-display

Seems odd that it doesn’t support the 'book__author' style format which is used everywhere else in the DB API.

Turns out there’s a ticket for this feature, which is marked as Won’t Fix.


回答 5

我刚刚发布了一个片段,使admin.ModelAdmin支持’__’语法:

http://djangosnippets.org/snippets/2887/

因此,您可以执行以下操作:

class PersonAdmin(RelatedFieldAdmin):
    list_display = ['book__author',]

基本上,这只是在做其他答案中描述的相同操作,但是它会自动处理(1)设置admin_order_field(2)设置short_description以及(3)修改查询集以避免每一行都有数据库命中。

I just posted a snippet that makes admin.ModelAdmin support ‘__’ syntax:

http://djangosnippets.org/snippets/2887/

So you can do:

class PersonAdmin(RelatedFieldAdmin):
    list_display = ['book__author',]

This is basically just doing the same thing described in the other answers, but it automatically takes care of (1) setting admin_order_field (2) setting short_description and (3) modifying the queryset to avoid a database hit for each row.


回答 6

您可以使用可调用对象在列表显示中显示所需的任何内容。它看起来像这样:

def book_author(object):
  返回object.book.author

类PersonAdmin(admin.ModelAdmin):
  list_display = [book_author,]

You can show whatever you want in list display by using a callable. It would look like this:


def book_author(object):
  return object.book.author

class PersonAdmin(admin.ModelAdmin):
  list_display = [book_author,]

回答 7

这个已经被接受了,但是如果还有其他假人(像我一样)没有立即从当前接受的答案中得到答案,那么这里有更多细节。

ForeignKey需要引用的模型类在其中需要一个__unicode__方法,例如:

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

    def __unicode__(self):
        return self.name

这对我来说很重要,应该适用于上述情况。这适用于Django 1.0.2。

This one’s already accepted, but if there are any other dummies out there (like me) that didn’t immediately get it from the presently accepted answer, here’s a bit more detail.

The model class referenced by the ForeignKey needs to have a __unicode__ method within it, like here:

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

    def __unicode__(self):
        return self.name

That made the difference for me, and should apply to the above scenario. This works on Django 1.0.2.


回答 8

如果您有很多关联属性字段供使用,list_display并且不想为每个函数创建一个函数(及其属性),那么一个肮脏而简单的解决方案将覆盖ModelAdmininstace __getattr__方法,即刻创建可调用对象:

class DynamicLookupMixin(object):
    '''
    a mixin to add dynamic callable attributes like 'book__author' which
    return a function that return the instance.book.author value
    '''

    def __getattr__(self, attr):
        if ('__' in attr
            and not attr.startswith('_')
            and not attr.endswith('_boolean')
            and not attr.endswith('_short_description')):

            def dyn_lookup(instance):
                # traverse all __ lookups
                return reduce(lambda parent, child: getattr(parent, child),
                              attr.split('__'),
                              instance)

            # get admin_order_field, boolean and short_description
            dyn_lookup.admin_order_field = attr
            dyn_lookup.boolean = getattr(self, '{}_boolean'.format(attr), False)
            dyn_lookup.short_description = getattr(
                self, '{}_short_description'.format(attr),
                attr.replace('_', ' ').capitalize())

            return dyn_lookup

        # not dynamic lookup, default behaviour
        return self.__getattribute__(attr)


# use examples    

@admin.register(models.Person)
class PersonAdmin(admin.ModelAdmin, DynamicLookupMixin):
    list_display = ['book__author', 'book__publisher__name',
                    'book__publisher__country']

    # custom short description
    book__publisher__country_short_description = 'Publisher Country'


@admin.register(models.Product)
class ProductAdmin(admin.ModelAdmin, DynamicLookupMixin):
    list_display = ('name', 'category__is_new')

    # to show as boolean field
    category__is_new_boolean = True

作为要点

可调用的特殊属性,例如booleanshort_description必须定义为ModelAdmin属性,例如book__author_verbose_name = 'Author name'category__is_new_boolean = True

callable admin_order_field属性是自动定义的。

不要忘记在您的列表中使用list_select_related属性,ModelAdmin以使Django避免常规查询。

If you have a lot of relation attribute fields to use in list_display and do not want create a function (and it’s attributes) for each one, a dirt but simple solution would be override the ModelAdmin instace __getattr__ method, creating the callables on the fly:

class DynamicLookupMixin(object):
    '''
    a mixin to add dynamic callable attributes like 'book__author' which
    return a function that return the instance.book.author value
    '''

    def __getattr__(self, attr):
        if ('__' in attr
            and not attr.startswith('_')
            and not attr.endswith('_boolean')
            and not attr.endswith('_short_description')):

            def dyn_lookup(instance):
                # traverse all __ lookups
                return reduce(lambda parent, child: getattr(parent, child),
                              attr.split('__'),
                              instance)

            # get admin_order_field, boolean and short_description
            dyn_lookup.admin_order_field = attr
            dyn_lookup.boolean = getattr(self, '{}_boolean'.format(attr), False)
            dyn_lookup.short_description = getattr(
                self, '{}_short_description'.format(attr),
                attr.replace('_', ' ').capitalize())

            return dyn_lookup

        # not dynamic lookup, default behaviour
        return self.__getattribute__(attr)


# use examples    

@admin.register(models.Person)
class PersonAdmin(admin.ModelAdmin, DynamicLookupMixin):
    list_display = ['book__author', 'book__publisher__name',
                    'book__publisher__country']

    # custom short description
    book__publisher__country_short_description = 'Publisher Country'


@admin.register(models.Product)
class ProductAdmin(admin.ModelAdmin, DynamicLookupMixin):
    list_display = ('name', 'category__is_new')

    # to show as boolean field
    category__is_new_boolean = True

As gist here

Callable especial attributes like boolean and short_description must be defined as ModelAdmin attributes, eg book__author_verbose_name = 'Author name' and category__is_new_boolean = True.

The callable admin_order_field attribute is defined automatically.

Don’t forget to use the list_select_related attribute in your ModelAdmin to make Django avoid aditional queries.


回答 9

PyPI中有一个非常易于使用的软件包,可以准确地处理该软件包:django-related-admin。您还可以在GitHub中查看代码

使用此功能,您想要实现的过程很简单:

class PersonAdmin(RelatedFieldAdmin):
    list_display = ['book__author',]

这两个链接均包含安装和使用的完整详细信息,因此,如果它们发生更改,我就不会在此处粘贴它们。

顺便提一句,如果您已经使用了其他工具model.Admin(例如,我正在使用SimpleHistoryAdmin),则可以执行以下操作:class MyAdmin(SimpleHistoryAdmin, RelatedFieldAdmin)

There is a very easy to use package available in PyPI that handles exactly that: django-related-admin. You can also see the code in GitHub.

Using this, what you want to achieve is as simple as:

class PersonAdmin(RelatedFieldAdmin):
    list_display = ['book__author',]

Both links contain full details of installation and usage so I won’t paste them here in case they change.

Just as a side note, if you’re already using something other than model.Admin (e.g. I was using SimpleHistoryAdmin instead), you can do this: class MyAdmin(SimpleHistoryAdmin, RelatedFieldAdmin).


回答 10

如果您在Inline中尝试,除非以下条件,否则您不会成功:

在您的内联中:

class AddInline(admin.TabularInline):
    readonly_fields = ['localname',]
    model = MyModel
    fields = ('localname',)

在您的模型(MyModel)中:

class MyModel(models.Model):
    localization = models.ForeignKey(Localizations)

    def localname(self):
        return self.localization.name

if you try it in Inline, you wont succeed unless:

in your inline:

class AddInline(admin.TabularInline):
    readonly_fields = ['localname',]
    model = MyModel
    fields = ('localname',)

in your model (MyModel):

class MyModel(models.Model):
    localization = models.ForeignKey(Localizations)

    def localname(self):
        return self.localization.name

回答 11

AlexRobbins的答案对我有用,除了前两行需要在模型中(也许这是假设的?),并且应该引用self:

def book_author(self):
  return self.book.author

然后管理部分可以很好地工作。

AlexRobbins’ answer worked for me, except that the first two lines need to be in the model (perhaps this was assumed?), and should reference self:

def book_author(self):
  return self.book.author

Then the admin part works nicely.


回答 12

我更喜欢这样:

class CoolAdmin(admin.ModelAdmin):
    list_display = ('pk', 'submodel__field')

    @staticmethod
    def submodel__field(obj):
        return obj.submodel.field

I prefer this:

class CoolAdmin(admin.ModelAdmin):
    list_display = ('pk', 'submodel__field')

    @staticmethod
    def submodel__field(obj):
        return obj.submodel.field

如何在Django queryset中执行OR条件?

问题:如何在Django queryset中执行OR条件?

我想编写一个与此SQL查询等效的Django查询:

SELECT * from user where income >= 5000 or income is NULL.

如何构造Django queryset过滤器?

User.objects.filter(income__gte=5000, income=0)

这是行不通的,因为它AND是过滤器。我想要OR过滤器以获取单个查询集的并集。

I want to write a Django query equivalent to this SQL query:

SELECT * from user where income >= 5000 or income is NULL.

How to construct the Django queryset filter?

User.objects.filter(income__gte=5000, income=0)

This doesn’t work, because it ANDs the filters. I want to OR the filters to get union of individual querysets.


回答 0

from django.db.models import Q
User.objects.filter(Q(income__gte=5000) | Q(income__isnull=True))

通过文档

from django.db.models import Q
User.objects.filter(Q(income__gte=5000) | Q(income__isnull=True))

via Documentation


回答 1

由于QuerySet实现了Python __or__运算符(|)或并集,因此它可以正常工作。正如您所期望的,|二进制运算符返回一个QuerySetso order_by(),,.distinct()其他查询集过滤器可以附加到末尾。

combined_queryset = User.objects.filter(income__gte=5000) | User.objects.filter(income__isnull=True)
ordered_queryset = combined_queryset.order_by('-income')

更新2019-06-20:现在在Django 2.1 QuerySet API参考中已完全记录了该内容。更多历史性讨论可以在DjangoProject票证#21333中找到

Because QuerySets implement the Python __or__ operator (|), or union, it just works. As you’d expect, the | binary operator returns a QuerySet so order_by(), .distinct(), and other queryset filters can be tacked on to the end.

combined_queryset = User.objects.filter(income__gte=5000) | User.objects.filter(income__isnull=True)
ordered_queryset = combined_queryset.order_by('-income')

Update 2019-06-20: This is now fully documented in the Django 2.1 QuerySet API reference. More historic discussion can be found in DjangoProject ticket #21333.


回答 2

现有答案中已经提到了这两种选择:

from django.db.models import Q
q1 = User.objects.filter(Q(income__gte=5000) | Q(income__isnull=True))

q2 = User.objects.filter(income__gte=5000) | User.objects.filter(income__isnull=True)

但是,似乎偏爱哪一个。

关键是它们在SQL级别上是相同的,所以请随意选择您喜欢的任何一个!

Django的ORM食谱在这个比较详细谈到,这里是有关部分:


queryset = User.objects.filter(
        first_name__startswith='R'
    ) | User.objects.filter(
    last_name__startswith='D'
)

导致

In [5]: str(queryset.query)
Out[5]: 'SELECT "auth_user"."id", "auth_user"."password", "auth_user"."last_login",
"auth_user"."is_superuser", "auth_user"."username", "auth_user"."first_name",
"auth_user"."last_name", "auth_user"."email", "auth_user"."is_staff",
"auth_user"."is_active", "auth_user"."date_joined" FROM "auth_user"
WHERE ("auth_user"."first_name"::text LIKE R% OR "auth_user"."last_name"::text LIKE D%)'

qs = User.objects.filter(Q(first_name__startswith='R') | Q(last_name__startswith='D'))

导致

In [9]: str(qs.query)
Out[9]: 'SELECT "auth_user"."id", "auth_user"."password", "auth_user"."last_login",
 "auth_user"."is_superuser", "auth_user"."username", "auth_user"."first_name",
  "auth_user"."last_name", "auth_user"."email", "auth_user"."is_staff",
  "auth_user"."is_active", "auth_user"."date_joined" FROM "auth_user"
  WHERE ("auth_user"."first_name"::text LIKE R% OR "auth_user"."last_name"::text LIKE D%)'

资料来源:django-orm-cookbook


Both options are already mentioned in the existing answers:

from django.db.models import Q
q1 = User.objects.filter(Q(income__gte=5000) | Q(income__isnull=True))

and

q2 = User.objects.filter(income__gte=5000) | User.objects.filter(income__isnull=True)

However, there seems to be some confusion regarding which one is to prefer.

The point is that they are identical on the SQL level, so feel free to pick whichever you like!

The Django ORM Cookbook talks in some detail about this, here is the relevant part:


queryset = User.objects.filter(
        first_name__startswith='R'
    ) | User.objects.filter(
    last_name__startswith='D'
)

leads to

In [5]: str(queryset.query)
Out[5]: 'SELECT "auth_user"."id", "auth_user"."password", "auth_user"."last_login",
"auth_user"."is_superuser", "auth_user"."username", "auth_user"."first_name",
"auth_user"."last_name", "auth_user"."email", "auth_user"."is_staff",
"auth_user"."is_active", "auth_user"."date_joined" FROM "auth_user"
WHERE ("auth_user"."first_name"::text LIKE R% OR "auth_user"."last_name"::text LIKE D%)'

and

qs = User.objects.filter(Q(first_name__startswith='R') | Q(last_name__startswith='D'))

leads to

In [9]: str(qs.query)
Out[9]: 'SELECT "auth_user"."id", "auth_user"."password", "auth_user"."last_login",
 "auth_user"."is_superuser", "auth_user"."username", "auth_user"."first_name",
  "auth_user"."last_name", "auth_user"."email", "auth_user"."is_staff",
  "auth_user"."is_active", "auth_user"."date_joined" FROM "auth_user"
  WHERE ("auth_user"."first_name"::text LIKE R% OR "auth_user"."last_name"::text LIKE D%)'

source: django-orm-cookbook



Django ORM中的select_related和prefetch_related有什么区别?

问题:Django ORM中的select_related和prefetch_related有什么区别?

在Django文件中,

select_related() “遵循”外键关系,在执行查询时选择其他相关对象数据。

prefetch_related() 对每个关系进行单独的查找,并在Python中执行“联接”。

“在python中进行连接”是什么意思?有人可以举例说明吗?

我的理解是,对于外键关系,使用select_related; 对于M2M关系,请使用prefetch_related。它是否正确?

In Django doc,

select_related() “follows” foreign-key relationships, selecting additional related-object data when it executes its query.

prefetch_related() does a separate lookup for each relationship, and does the “joining” in Python.

What does it mean by “doing the joining in python”? Can someone illustrate with an example?

My understanding is that for foreign key relationship, use select_related; and for M2M relationship, use prefetch_related. Is this correct?


回答 0

您的理解基本上是正确的。您可以使用select_related时,你将要选择的对象是一个对象,所以OneToOneField还是ForeignKey。您可以使用prefetch_related时,你会得到一个东西的“设置”,所以ManyToManyFieldS作为你陈述或反向ForeignKey秒。为了阐明我的意思是“ reverse ForeignKeys”,这里有一个例子:

class ModelA(models.Model):
    pass

class ModelB(models.Model):
    a = ForeignKey(ModelA)

ModelB.objects.select_related('a').all() # Forward ForeignKey relationship
ModelA.objects.prefetch_related('modelb_set').all() # Reverse ForeignKey relationship

区别在于select_related执行SQL连接,因此从SQL Server将结果作为表的一部分返回。prefetch_related另一方面,执行另一个查询,因此减少了原始对象中的冗余列(ModelA在上面的示例中)。您可以使用prefetch_related任何可以使用的东西select_related

折衷方案是prefetch_related必须创建并发送ID列表以选择回服务器,这可能需要一段时间。我不确定在事务中是否有很好的方法,但是我的理解是Django总是只发送一个列表并显示SELECT … WHERE PK IN(…,…,…)基本上。在这种情况下,如果预取的数据稀疏(例如,将美国国家对象链接到人们的地址),这可能会很好,但是,如果它们之间的关系更接近一对一,则会浪费大量通信资源。如有疑问,请尝试两者并查看哪种效果更好。

上面讨论的所有内容基本上都与与数据库的通信有关。但是,在Python方面prefetch_related具有额外的好处,即使用单个对象表示数据库中的每个对象。使用select_related重复的对象将在Python中为每个“父”对象创建。由于Python中的对象具有相当大的内存开销,因此这也是一个考虑因素。

Your understanding is mostly correct. You use select_related when the object that you’re going to be selecting is a single object, so OneToOneField or a ForeignKey. You use prefetch_related when you’re going to get a “set” of things, so ManyToManyFields as you stated or reverse ForeignKeys. Just to clarify what I mean by “reverse ForeignKeys” here’s an example:

class ModelA(models.Model):
    pass

class ModelB(models.Model):
    a = ForeignKey(ModelA)

ModelB.objects.select_related('a').all() # Forward ForeignKey relationship
ModelA.objects.prefetch_related('modelb_set').all() # Reverse ForeignKey relationship

The difference is that select_related does an SQL join and therefore gets the results back as part of the table from the SQL server. prefetch_related on the other hand executes another query and therefore reduces the redundant columns in the original object (ModelA in the above example). You may use prefetch_related for anything that you can use select_related for.

The tradeoffs are that prefetch_related has to create and send a list of IDs to select back to the server, this can take a while. I’m not sure if there’s a nice way of doing this in a transaction, but my understanding is that Django always just sends a list and says SELECT … WHERE pk IN (…,…,…) basically. In this case if the prefetched data is sparse (let’s say U.S. State objects linked to people’s addresses) this can be very good, however if it’s closer to one-to-one, this can waste a lot of communications. If in doubt, try both and see which performs better.

Everything discussed above is basically about the communications with the database. On the Python side however prefetch_related has the extra benefit that a single object is used to represent each object in the database. With select_related duplicate objects will be created in Python for each “parent” object. Since objects in Python have a decent bit of memory overhead this can also be a consideration.


回答 1

两种方法可以达到相同的目的,从而放弃不必要的数据库查询。但是他们使用不同的方法来提高效率。

使用这两种方法的唯一原因是,当单个大型查询优于许多小型查询时。Django使用大型查询来抢先在内存中创建模型,而不是针对数据库执行按需查询。

select_related对每个查找执行联接,但将选择范围扩展为包括所有联接表的列。但是,这种方法有一个警告。

联接有可能使查询中的行数相乘。当您通过外键或一对一字段执行联接时,行数不会增加。但是,多对多联接没有此保证。因此,Django限制select_related了不会意外导致大规模联接的关系。

对于“ join in python”来说prefetch_related,应该比它还要令人震惊。它为要连接的每个表创建一个单独的查询。它使用WHERE IN子句过滤每个表,例如:

SELECT "credential"."id",
       "credential"."uuid",
       "credential"."identity_id"
FROM   "credential"
WHERE  "credential"."identity_id" IN
    (84706, 48746, 871441, 84713, 76492, 84621, 51472);

每个表都被拆分成一个单独的查询,而不是执行可能包含太多行的单个联接。

Both methods achieve the same purpose, to forego unnecessary db queries. But they use different approaches for efficiency.

The only reason to use either of these methods is when a single large query is preferable to many small queries. Django uses the large query to create models in memory preemptively rather than performing on demand queries against the database.

select_related performs a join with each lookup, but extends the select to include the columns of all joined tables. However this approach has a caveat.

Joins have the potential to multiply the number of rows in a query. When you perform a join over a foreign key or one-to-one field, the number of rows won’t increase. However, many-to-many joins do not have this guarantee. So, Django restricts select_related to relations that won’t unexpectedly result in a massive join.

The “join in python” for prefetch_related is a little more alarming then it should be. It creates a separate query for each table to be joined. It filters each of these table with a WHERE IN clause, like:

SELECT "credential"."id",
       "credential"."uuid",
       "credential"."identity_id"
FROM   "credential"
WHERE  "credential"."identity_id" IN
    (84706, 48746, 871441, 84713, 76492, 84621, 51472);

Rather than performing a single join with potentially too many rows, each table is split into a separate query.


回答 2

如Django文档所述:

prefetch_related()

返回一个QuerySet,该查询集将自动为每个指定的查询分批检索相关对象。

这与select_related具有相似的目的,因为两者均旨在阻止由于访问相关对象而导致的数据库查询泛滥,但是策略却大不相同。

select_related通过创建SQL连接并将相关对象的字段包括在SELECT语句中来工作。因此,select_related在同一数据库查询中获取相关对象。但是,为了避免跨“许多”关系进行联接会产生更大的结果集,select_related仅限于单值关系-外键和一对一关系。

另一方面,prefetch_related对每个关系进行单独的查找,并在Python中进行“联接”。除了select_related支持的外键和一对一关系之外,这还允许它预取多对多和多对一对象,这不能使用select_related完成。它还支持GenericRelation和GenericForeignKey的预取,但是,必须将其限制为同类结果。例如,仅当查询仅限于一个ContentType时,才支持预取GenericForeignKey引用的对象。

有关此的更多信息:https : //docs.djangoproject.com/en/2.2/ref/models/querysets/#prefetch-related

As Django documentation says:

prefetch_related()

Returns a QuerySet that will automatically retrieve, in a single batch, related objects for each of the specified lookups.

This has a similar purpose to select_related, in that both are designed to stop the deluge of database queries that is caused by accessing related objects, but the strategy is quite different.

select_related works by creating an SQL join and including the fields of the related object in the SELECT statement. For this reason, select_related gets the related objects in the same database query. However, to avoid the much larger result set that would result from joining across a ‘many’ relationship, select_related is limited to single-valued relationships – foreign key and one-to-one.

prefetch_related, on the other hand, does a separate lookup for each relationship, and does the ‘joining’ in Python. This allows it to prefetch many-to-many and many-to-one objects, which cannot be done using select_related, in addition to the foreign key and one-to-one relationships that are supported by select_related. It also supports prefetching of GenericRelation and GenericForeignKey, however, it must be restricted to a homogeneous set of results. For example, prefetching objects referenced by a GenericForeignKey is only supported if the query is restricted to one ContentType.

More information about this: https://docs.djangoproject.com/en/2.2/ref/models/querysets/#prefetch-related


回答 3

仔细阅读已经发布的答案。只是认为如果我添加一个带有实际示例的答案会更好。

假设您有3个相关的Django模型。

class M1(models.Model):
    name = models.CharField(max_length=10)

class M2(models.Model):
    name = models.CharField(max_length=10)
    select_relation = models.ForeignKey(M1, on_delete=models.CASCADE)
    prefetch_relation = models.ManyToManyField(to='M3')

class M3(models.Model):
    name = models.CharField(max_length=10)

在这里,您可以使用字段和使用字段的对象查询M2模型及其相关M1对象。select_relationM3prefetch_relation

但是正如我们所提到M1的关系由M2ForeignKey,它只返回只有1对任何记录M2对象。同样的事情也适用OneToOneField

但是M3与的关系来自M2ManyToManyField它可能返回任意数量的M1对象。

考虑这样一种情况:您有2个M2对象m21m22这些对象具有相同的5个M3具有ID的关联对象1,2,3,4,5。当您M3为每个对象获取关联的M2对象时,如果使用select related,则它将如何工作。

脚步:

  1. 查找m21对象。
  2. 查询M3m21ID为的对象相关的所有对象1,2,3,4,5
  3. m22对象和所有其他M2对象重复相同的操作。

因为我们有相同1,2,3,4,5的ID两个m21m22对象,如果我们使用select_related选项,它会查询数据库两次,这已经获取相同的ID。

相反,如果您使用prefetch_related,则当您尝试获取M2对象时,它将在查询M2表时记下对象返回的所有ID(注意:仅这些ID),并且作为最后一步,Django将对M3表进行查询以及您的M2对象已返回的所有ID的集合。并M2使用Python而不是数据库将它们连接到对象。

这样,您M3只查询一次所有对象,从而提高了性能。

Gone through the already posted answers. Just thought it would be better if I add an answer with actual example.

Let’ say you have 3 Django models which are related.

class M1(models.Model):
    name = models.CharField(max_length=10)

class M2(models.Model):
    name = models.CharField(max_length=10)
    select_relation = models.ForeignKey(M1, on_delete=models.CASCADE)
    prefetch_relation = models.ManyToManyField(to='M3')

class M3(models.Model):
    name = models.CharField(max_length=10)

Here you can query M2 model and its relative M1 objects using select_relation field and M3 objects using prefetch_relation field.

However as we’ve mentioned M1‘s relation from M2 is a ForeignKey, it just returns only 1 record for any M2 object. Same thing applies for OneToOneField as well.

But M3‘s relation from M2 is a ManyToManyField which might return any number of M1 objects.

Consider a case where you have 2 M2 objects m21, m22 who have same 5 associated M3 objects with IDs 1,2,3,4,5. When you fetch associated M3 objects for each of those M2 objects, if you use select related, this is how it’s going to work.

Steps:

  1. Find m21 object.
  2. Query all the M3 objects related to m21 object whose IDs are 1,2,3,4,5.
  3. Repeat same thing for m22 object and all other M2 objects.

As we have same 1,2,3,4,5 IDs for both m21, m22 objects, if we use select_related option, it’s going to query the DB twice for the same IDs which were already fetched.

Instead if you use prefetch_related, when you try to get M2 objects, it will make a note of all the IDs that your objects returned (Note: only the IDs) while querying M2 table and as last step, Django is going to make a query to M3 table with the set of all IDs that your M2 objects have returned. and join them to M2 objects using Python instead of database.

This way you’re querying all the M3 objects only once which improves performance.