Django如何允许在IntegerRangeField

时间:2017-05-02 21:43:41

标签: python django postgresql django-filter

我使用IntegerRangeField(PostgreSQL的特定字段)来表示发布bancnote时的年份。

在向db添加对象并使用非空范围(例如1990-2000)进行过滤时工作正常,但是很少有bancnotes,仅在一年内发布,因此该期间例如是2005-2005。设置此范围时,在将对象添加到db。

后,此值将变为“None-None”

似乎IntegerRangeField不接受空范围。

我试图将范围设置为'2005-(此处没有值)',空白= True,null = True,这对我来说很好,但在这种情况下,过滤器不能用于此对象。

要更清楚地看下面的例子:

  • 2003-2008:正确显示和过滤
  • 2003-2003:显示为“无 - 无”,过滤错误
  • 2003-(没有价值)/(没有价值)-2003:被称为'2003-None'/'None-2003'(对我来说是令人满意的),过滤器不起作用

还在考虑使用DateRangeField,但它在输入的同时提供日期和月份,这让人感到很乱。还有其他可行和正确的方法吗?

希望我对自己的情况很清楚,期待任何建议。请随时问,我会提供任何信息。提前谢谢!

这是我的 models.py

from django.contrib.postgres.fields import IntegerRangeField
from django.core.validators import MinValueValidator, MaxValueValidator
from django.db import models
from datetime import datetime


class Bancnote(models.Model):
    Dollar= 'Dollar'
    Euro= 'Euro'

    TYPE_CHOICES = (
        (Dollar, 'Dollar'),
        (Euro, 'Euro')
    )

    type = models.CharField(max_length=11, choices=TYPE_CHOICES, default=Dollar)
    par = models.PositiveIntegerField()
    year = IntegerRangeField(null=True, blank=True)
    size = models.CharField(max_length=7)
    sign = models.CharField(max_length=20)
    desc = models.TextField(max_length=200)
    image = models.ImageField(upload_to='bons_images')

    def __str__(self):
        return str(self.par) + ' ' + self.type + ' ' + str(self.year.lower) + '-' + str(self.year.upper)

filters.py

from django import forms
import django_filters

from .models import Bancnote


class BancnoteFilter(django_filters.FilterSet):
    type = django_filters.ChoiceFilter(name='type', choices=Bancnote.TYPE_CHOICES,
                                       widget=forms.RadioSelect(attrs={'class': 'radio'}), empty_label=None)
    par_gt = django_filters.NumberFilter(name='par', lookup_expr='gte', widget=forms.Select)
    par_lt = django_filters.NumberFilter(name='par', lookup_expr='lte', widget=forms.Select)
    year = django_filters.NumericRangeFilter(name='year', lookup_expr='contained_by')

    class Meta:
        model = Bancnote
        fields = ['type', 'par_gt', 'par_lt', 'year']

views.py

from django.shortcuts import render, render_to_response
from django.template import RequestContext
from django.shortcuts import render

from .models import Bancnote
from .filters import BancnoteFilter


def index(request):
    bons_list = Bancnote.objects.all().order_by('par')
    bons_filter = BancnoteFilter(request.GET, queryset=bons_list)


def image(request):
    bons = Bancnote()
    variables = RequestContext(request, {
        'bons': bons
    })
    return render_to_response('catalogue/bon_detail.html', variables)

的index.html

{% extends 'catalogue/base.html' %}
{% block title %}Catalogue{% endblock %}

{% load widget_tweaks %}

{% block sidebar %}
    <form method="get">
        <div class="well bs-sidebar" id="style" style="background-color:#fff">
            <ul class="nav nav-pills nav-stacked">
                <li>
                    <a href="#" class="toggle-menu" onclick="showcontent('#money-type')">Bancnote type
                        <i class="fa fa-chevron-up"></i>
                    </a>
                </li>
                <div id="money-type">
                    <ul class="nav nav-pills nav-stacked">
                        {% for type in filter.form.type %}
                            <li><a href="#">
                                {{ type.tag }}
                                <label for="{{ type.id_for_label }}">{{ type.choice_label }}</label>
                            </a></li>
                        {% endfor %}
                    </ul>
                </div>
                <li>
                    <a href="#" class="toggle-menu" onclick="showcontent('#par')">Bancnote par
                        <i class="fa fa-chevron-up"></i>
                    </a>
                </li>
                <div id="par">
                    {% render_field filter.form.par_gt id='from' %}{% render_field filter.form.par_lt id='to' %}
                </div>
                <li><a href="#" class="toggle-menu" onclick="showcontent('#period')">Issue years
                    <i class="fa fa-chevron-up"></i></a></li>
                <div id="period">
                    <div class="range-input">
                        {% render_field filter.form.year maxlength='4' %}
                    </div>
                    <div class="range-slider">
                        <input value="1917" min="1917" max="2017" step="1" type="range">
                        <input value="2017" min="1917" max="2017" step="1" type="range">
                    </div>
                </div>
            </ul>
        <button type="submit" class="btn btn-primary" style="text-align: center; width: 100%">
            <span class="glyphicon glyphicon-search"></span> Search
        </button>
        </div>
    </form>
{% endblock %}

{% block content %}

        <div class="container-fluid">
            <div class="row">
{# filtering here #}
                {% for bon in filter.qs %}
                    {% if forloop.counter0|divisibleby:"4" %}
            </div>
                        <div class="row">
                    {% endif %}
                    <div class="col-sm-3 col-lg-3">
                        <div style="display: block; text-align: center; margin: 0 auto">
                            <a href="{{ bon.id }}">
                                <img src="{{ bon.image.url }}" style="width: 50%; height: 50%"/>
                                <h5>{{ bon.par }} {{ bon.type }} {{ bon.year.lower}}-{{ bon.year.upper }}</h5>
                            </a>
                        </div>
                    </div>
                {% endfor %}
                        </div>
        </div>

{% endblock %}

1 个答案:

答案 0 :(得分:2)

主要问题是您没有正确存储范围。根据django postgres docs

  

所有范围字段都在python中转换为psycopg2 Range objects,但如果不需要边界信息,也会接受元组作为输入。默认值为包含下限,上限排除;也就是[)

简而言之,

  • tuple(2005, None)无效,因为postgres有效地将空边界视为无穷大。过滤失败,因为无限远在你的边界检查之外。
  • tuple(2005, 2005)无效,因为Django默认为排除的上限。 Postgres将[2005,2005)标准化为空,因为范围实际上并不存在。
  • 此外,tuple(2003, 2008)的范围不正确,因为2008年不包括在内。我很确定如果你过滤了2008年到2010年的范围,它将被排除在外。

要表示一年,您要使用:

  • tuple(2005, 2006)
  • Range(2005, 2005, bounds='[]')请注意,bounds是一个字符串文字(psycopg2 docs)。

关于过滤,过滤器会执行startwsithendswith检查,我认为这是包含。因此,在2005年到2005年之间过滤您的年份应该从DB中获得预期值。正确存储范围后,过滤器正常运行。

Postgres范围docs以供参考。第8.17.5节包含有关边界行为和标准化的信息。

示例范围:

from psycopg2.extras import NumericRange

NumericRange(2005, 2008, bounds='[]')