# 第六章- 模型管理 ******************* 本章我们覆盖以下议题: Customizing columns in the change list page Creating admin actions Developing change list filters Exchanging administration settings for external apps Inserting a map into a change form ## 引言 The Django framework comes with a built-in administration system for your models. With very little effort, you can set up filterable, searchable, and sortable lists for browsing your models, and configure forms for adding and editing data. In this chapter, we will go through advanced techniques to customize administration by developing some practical cases. Django框架为你的模型提供了一个内建的管理系统。只需少许努力你就可以为模型浏览配置一个可过滤的,可搜索的,可排序的列表,以及配置可以添加和编辑数据的表单。在这一章,我们会通过编写一个实际的例子来彻底了解定制管理所需的高级技术。 ## 定制切换列表页面的中的列 Change list views in the default Django administration system let you have an overview of all instances of specific models. By default, the model admin property, `list_display`, controls which fields to show in different columns. But additionally, you can have custom functions set there that return data from relations or display custom HTML. In this recipe, we will create a special function for the `list_display` property that shows an image in one of the columns of the list view. As a bonus, we will make one field editable directly in the list view by adding the `list_editable` setting. 改变默认的Django管理系统中的列表试图能够让你拥有一个特定模型的全部实例的概览。默认,模型admin的特性`list_display`控制着在不同的列中哪一个字段会被显示。此外,你可以定制函数 ## 开始前的准备 To start with, make sure that django.contrib.admin is in `INSTALLED_APPS` in the settings, and AdminSite is hooked into the URL configuration. Then, create a new app named products and put it under `INSTALLED_APPS`. This app will have the Product and ProductPhoto models, where one product might have multiple photos. For this example, we will also be using UrlMixin, which was defined in the Creating a model mixin with URL-related methods recipe in Chapter 2, Database Structure. 要准备开始的话,请确保`django.contrib.admin`在设置文件的`INSTALLED_APPS`中,而且Admin站点已经挂在URL配置中了。然后创建一个新的名称为products的应用并把它放到`INSTALLED_APPS`中去。该应用拥有模型`Prodcut`和`ProdcutPhoto`,这里一个procut可能拥有多张照片。就我们的这个例子而言,我们也会用到UrlMixin, Let's create the Product and ProductPhoto models in the models.py file as follows: 如下,我们在models.py文件中模型Prodcut和ProdcutPhoto: ```python #products/models.py # -*- coding: UTF-8 -*- import os from django.db import models from django.utils.timezone import now as timezone_now from django.utils.translation import ugettext_lazy as _ from django.core.urlresolvers import reverse from django.core.urlresolvers import NoReverseMatch from utils.models import UrlMixin def upload_to(instance, filename): now = timezone_now() filename_base, filename_ext = os.path.splitext(filename) return "products/%s/%s%s" % ( instance.product.slug, now.strftime("%Y%m%d%H%M%S"), filename_ext.lower(), ) class Product(UrlMixin): title = models.CharField(_("title"), max_length=200) slug = models.SlugField(_("slug"), max_length=200) description = models.TextField(_("description"), blank=True) price = models.DecimalField(_(u"price (€)"), max_digits=8, decimal_places=2, blank=True, null=True) class Meta: verbose_name = _("Product") verbose_name_plural = _("Products") def __unicode__(self): return self.title def get_url_path(self): try: return reverse("product_detail", kwargs={ "slug": self.slug}) except NoReverseMatch: return "" class ProductPhoto(models.Model): product = models.ForeignKey(Product) photo = models.ImageField(_("photo"), upload_to=upload_to) class Meta: verbose_name = _("Photo") verbose_name_plural = _("Photos") def __unicode__(self): return self.photo.name ``` ## 具体做法 We will create a simple administration for the Product model that will have instances of the ProductPhoto model attached to the product as inlines. In the `list_display` property, we will define the get_photo method name of the model admin that will be used to show the first photo from the many-to-one relationship. Let's create an admin.py file with the following content: ```python #products/admin.py # -*- coding: UTF-8 -*- from django.db import models from django.contrib import admin from django.utils.translation import ugettext_lazy as _ from django.http import HttpResponse from products.models import Product, ProductPhoto class ProductPhotoInline(admin.StackedInline): model = ProductPhoto extra = 0 class ProductAdmin(admin.ModelAdmin): list_display = ["title", "get_photo", "price"] list_editable = ["price"] fieldsets = ( (_("Product"), { "fields": ("title", "slug", "description", "price"), }), ) prepopulated_fields = {"slug": ("title",)} inlines = [ProductPhotoInline] def get_photo(self, obj): project_photos = obj.productphoto_set.all()[:1] if project_photos.count() > 0: return u"""<a href="%(product_url)s" target="_blank"> <img src="%(photo_url)s" alt="" width="100" /> </a>""" % { "product_url": obj.get_url_path(), "photo_url": project_photos[0].photo.url, } return u"" get_photo.short_description = _("First photo") get_photo.allow_tags = True admin.site.register(Product, ProductAdmin) ``` ## 工作原理 If you look at the product administration list in the browser, it will look like this: 图片:略 Besides the normal field names, the `list_display` property accepts a function or another callable, the name of an attribute of the admin model, or the name of the attribute of the model. Each callable will be passed a model instance as the first argument. So, in our example, we have the `get_photo` method of the model admin that retrieves Product as obj. The method tries to get the first ProductPhoto from the `many-to-one` relation and, if it exists, it returns HTML with the <img> tag linked to the detail page of Product. The `short_description` property of the callable defines the title shown for the column. The allow_tags property tells the administration not to escape the HTML values. In addition, the price field is made editable by the `list_editable` setting and there is a save button at the bottom to save the whole list of products. ## 参阅 - The Creating a model mixin with URL-related methods recipe in Chapter 2, Database Structure - The Creating admin actions recipe - The Developing change list filters recipe ## 添加Admin的行为 The Django administration system provides actions that we can execute for selected items in the list. There is one action given by default, and it is used to delete selected instances. In this recipe, we will create an additional action for the list of the Product model that allows administrators to export selected products to Excel spreadsheets. ## 开始前的准备 我们就从之前方法中所创建的应用`products`开始。 ## 具体做法 Admin actions are functions that take three arguments: the current ModelAdmin value, the current HttpRequest value, and the QuerySet value containing the selected items. Perform the following steps: Let's create an export_xls function in the admin.py file of the products app, as follows: ```python #products/admin.py # -*- coding: UTF-8 -*- import xlwt # ... other imports ... def export_xls(modeladmin, request, queryset): response = HttpResponse(mimetype="application/ms-excel") response["Content-Disposition"] = "attachment; "\ "filename=products.xls" wb = xlwt.Workbook(encoding="utf-8") ws = wb.add_sheet("Products") row_num = 0 ### Print Title Row ### columns = [ # column name, column width (u"ID", 2000), (u"Title", 6000), (u"Description", 8000), (u"Price (€)", 3000), ] header_style = xlwt.XFStyle() header_style.font.bold = True for col_num in xrange(len(columns)): ws.write(row_num, col_num, columns[col_num][0], header_style) # set column width ws.col(col_num).width = columns[col_num][1] ### Print Content ### text_style = xlwt.XFStyle() text_style.alignment.wrap = 1 price_style = xlwt.XFStyle() price_style.num_format_str = "0.00" styles = [text_style, text_style, text_style, price_style] for obj in queryset.order_by("pk"): row_num += 1 row = [ obj.pk, obj.title, obj.description, obj.price, ] for col_num in xrange(len(row)): ws.write(row_num, col_num, row[col_num], styles[col_num]) wb.save(response) return response export_xls.short_description = u"Export XLS” ``` 2. Then, add the actions setting to ProductAdmin, as follows: ```python class ProductAdmin(admin.ModelAdmin): # ... actions = [export_xls] ``` ## 工作原理 If you look at the product administration list page in the browser, you will see a new action called Export XLS along with the default action Delete selected Products: 图片:略 By default, admin actions do something with QuerySet and redirect the administrator back to the change list page. However, for some more complex actions like this, HttpResponse can be returned. The export_xls function returns HttpResponse with the MIME type of Excel spreadsheet. Using the Content-Disposition header, we set the response to be downloadable with the file named products.xls. Then, we use the xlwt Python module to create the Excel file. At first, the workbook with UTF-8 encoding is created. Then, we add a sheet named Products to it. We will be using the write method of the sheet to set the content and style for each cell and the col method to retrieve the column and set the width to it. To have an overview of all columns in the sheet, we create a list of tuples with column names and widths. Excel uses some magical units for the widths of the columns. They are 1/256 of the width of the zero character for the default font. Next, we define the header style to be bold. As we have the columns defined, we loop through them and fill the first row with the column names, also assigning the bold style to them. Then, we create a style for normal cells and for the prices. The text in normal cells will be wrapped in multiple lines. Prices will have a special number style with two points after the decimal point. Lastly, we go through the QuerySet of the selected products ordered by ID and print the specified fields into corresponding cells, also applying specific styles. The workbook is saved to the file-like HttpResponse object and the resulting Excel sheet looks like this: 表格:略 ## 参阅 - Chapter 9, Data Import and Export - The Customizing columns in the change list page recipe - The Developing change list filters recipe ## 开发