如何在Django Admin中比较两个内联?

时间:2014-02-28 00:23:05

标签: django django-admin

今天我遇到了一个我认为只能用Django解决的问题。

我有以下admin.py:

# encoding: utf-8
from django.contrib import admin
from django import forms
from django.utils.translation import ugettext_lazy as _

from . models import Service, ServiceItem, Optional, \
    Subscription, SubscriptionItem, SubscriptionConsumption


class ServiceItemInline(admin.StackedInline):
    model = ServiceItem
    fields = ('optional', 'price', 'price_excess', 'currency',
              'start_date', 'account',
              'country', 'state', 'region', 'city')
    extra = 1


class ServiceAdmin(admin.ModelAdmin):
    fields = ('name', 'short_name', 'unit', 'optionals')
    inlines = [ServiceItemInline, ]


class BaseSubscriptionFormSet(forms.models.BaseInlineFormSet):

    def clean(self):
        super(BaseSubscriptionFormSet, self).clean()

        total_consumption = 0.0
        total_burst = 0.0

        for form in self.forms:
            if not form.is_valid():
                return
            if form.cleaned_data:
                if not form.cleaned_data['optional']:
                    total_consumption = total_consumption + \
                        form.cleaned_data['consumption_value']
                    total_burst = total_burst + \
                        form.cleaned_data['burst_value']
            # Raises error if values are not the same
            if total_consumption != self.instance.consumption_value:
                raise forms.ValidationError(_('Sum of all Consumption values '
                                              'must be same as '
                                              'defined in Subscription.'))
            if total_burst != self.instance.burst_value:
                raise forms.ValidationError(_('Sum of all Burst values '
                                              'must be same as '
                                              'defined in Subscription.'))


class ServiceConsumptionInline(admin.TabularInline):
    model = SubscriptionConsumption
    fields = ('optional', 'consumption_value', 'burst_value', 'burst_interval')


class SubscriptionContractInline(admin.StackedInline):
    model = SubscriptionItem
    formset = BaseSubscriptionFormSet
    fields = ('optional', 'price', 'price_excess', 'currency',
              'start_date', 'account',
              'country', 'state', 'region', 'city',
              'consumption_value', 'burst_value', 'burst_interval',
              'end_date', 'is_active')


class SubscriptionAdmin(admin.ModelAdmin):
    inlines = [ServiceConsumptionInline, SubscriptionContractInline, ]


admin.site.register(Optional)
admin.site.register(Service, ServiceAdmin)
admin.site.register(Subscription, SubscriptionAdmin)

我的SubscriptionAdmin有2个内联。我想比较这些内联。如果没有Javascript,它甚至可以吗?

这里更清楚的是我的models.py:

# encoding: utf-8
import datetime
from django.db import models
from django.core.validators import MinValueValidator
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _

from apps.account.models import (
    Account, Country, Currency
)
from apps.client.models import Client


class Item(models.Model):
    price = models.FloatField(validators=[MinValueValidator(0.0)],
                              help_text=_('Unit price'))
    price_excess = models.FloatField(validators=[MinValueValidator(0.0)],
                                     help_text=_('Unit price for excess.'))
    currency = models.ForeignKey(Currency)
    account = models.ForeignKey(Account)
    country = models.ForeignKey(Country, null=True, blank=True)
    state = models.CharField(max_length=250, blank=True)
    region = models.CharField(max_length=250, blank=True)
    city = models.CharField(max_length=250, blank=True)
    start_date = models.DateField(help_text=_('Subscription start date item.'))

    class Meta:
        abstract = True


class Optional(models.Model):

    UNIT_CHOICES = (
        (1, 'Giga Bytes'),
        (2, 'Hits'),
        (3, 'Counter'),
        (4, 'Feature'),
    )

    name = models.CharField(
        max_length=250, help_text=_('e.g. SSL Custom Domain.'))
    unit = models.PositiveSmallIntegerField(max_length=250,
                                            choices=UNIT_CHOICES)

    def __unicode__(self):
        return u'{0} ({1})'.format(self.name, self.get_unit_display())


class Service(models.Model):

    UNIT_CHOICES = (
        (1, 'Giga Bytes'),
    )

    SHORT_NAME_CHOICES = (
        (1, 'PH'),
        (2, 'HS'),
        (3, 'LS'),
        (4, 'HC'),
    )

    name = models.CharField(max_length=250,
                            help_text=_('e.g. HTTP Caching'))
    short_name = models.PositiveSmallIntegerField(max_length=2,
                                                  unique=True,
                                                  choices=SHORT_NAME_CHOICES)
    unit = models.PositiveSmallIntegerField(max_length=250,
                                            choices=UNIT_CHOICES)
    optionals = models.ManyToManyField(Optional)

    def __unicode__(self):
        return self.name


class ServiceItem(Item):
    service = models.ForeignKey(Service)
    optional = models.ForeignKey(Optional, null=True, blank=True)
    created_by = models.ForeignKey(Account,
                                   null=True,
                                   blank=True,
                                   related_name='services')

    def __unicode__(self):
        return self.service.name

    def clean(self):
        # Checks if this object already exists.
        # TODO: refactor this unique value validation.
        qs = ServiceItem.objects.exclude(id=self.id)
        qs = qs.filter(service=self.service,
                       optional=self.optional,
                       currency=self.currency,
                       account=self.account,
                       country=self.country,
                       state=self.state,
                       region=self.region,
                       city=self.city,
                       start_date=self.start_date)
        if qs.exists():
            raise ValidationError(_('Service Item should be unique.'))

    def get_absolute_url(self):
        return reverse('service:service_item_edit', args=[self.id])


class Subscription(models.Model):
    service = models.ForeignKey(Service)
    client = models.ForeignKey(Client)

    consumption_value = models.FloatField(
        validators=[MinValueValidator(0.0)],
        default=0,
        help_text=_('Max value for consumption, after that produces excess.'))
    burst_value = models.FloatField(
        validators=[MinValueValidator(0.0)],
        default=0,
        help_text=_('Max burst value, after that produces an excess.'))
    burst_interval = models.FloatField(
        validators=[MinValueValidator(0.0)],
        default=0,
        help_text=_('Interval of burst value in seconds.'))

    def __unicode__(self):
        return self.service.name


class SubscriptionConsumption(models.Model):
    subscription = models.ForeignKey(Subscription)
    optional = models.ForeignKey(Optional)

    consumption_value = models.FloatField(
        validators=[MinValueValidator(0.0)],
        default=0,
        help_text=_('Max value for consumption, after that produces excess.'))
    burst_value = models.FloatField(
        validators=[MinValueValidator(0.0)],
        default=0,
        help_text=_('Max burst value, after that produces an excess.'))
    burst_interval = models.FloatField(
        validators=[MinValueValidator(0.0)],
        default=0,
        help_text=_('Interval of burst value in seconds'))

    class Meta:
        unique_together = ('optional', 'subscription', )

    def __unicode__(self):
        return self.subscription.service.name


class SubscriptionManager(models.Manager):
    def with_optionals(self):
        return self.filter(optional__isnull=False)

    def without_optionals(self):
        return self.filter(optional__isnull=True)


class SubscriptionItem(Item):
    subscription = models.ForeignKey(Subscription)
    optional = models.ForeignKey(Optional, null=True, blank=True)

    objects = SubscriptionManager()

    consumption_value = models.FloatField(
        validators=[MinValueValidator(0.0)],
        default=0,
        null=True,
        blank=True,
        help_text=_('Max value for consumption, after that produces excess.'))
    burst_value = models.FloatField(
        validators=[MinValueValidator(0.0)],
        default=0,
        null=True,
        blank=True,
        help_text=_('Max burst value, after that produces an excess.'))
    burst_interval = models.FloatField(
        validators=[MinValueValidator(0.0)],
        default=0,
        null=True,
        blank=True,
        help_text=_('Interval of burst value in seconds'))
    end_date = models.DateField(
        null=True,
        blank=True,
        help_text=_('After hitting end date, subscription will no '
                    'longer be active.'))
    is_active = models.BooleanField(
        _('Active'), default=True,
        help_text=_('If your subscription is deactivated, '
                    'it will no longer work.'))

    class Meta:
        unique_together = (('subscription', 'optional',
                            'account', 'country',
                            'state', 'region', 'city', 'start_date'))

    def __unicode__(self):
        return self.subscription.service.name

    def clean(self):
        # Checks if this subscription belongs to Service settings.
        if self.optional:
            if self.optional not in self.subscription.service.optionals.all():
                raise ValidationError(
                    _('This optional is not available for this subscription.'))

    def is_expired(self):
        """Returns True if Subscription has end_date
        less than today or if is_active is True."""
        diff = datetime.datetime.utcnow() - self.end_date.replace(tzinfo=None)
        if diff > 0:
            return True
        else:
            return False

用例:

添加SubscriptionItem时,我应该能够将ServiceConsumptionInline与SubscriptionContractInline进行比较,执行一些计算并检查值是否正常。如果他们对我的业务没问题就会引发ValidationError。

我做了很多搜索,但我没有发现类似的问题。我尝试了IRC但没有成功。

1 个答案:

答案 0 :(得分:0)

使用custom formset validation

  

formset有一个类似于Form类的方法。这个   是您在formset上定义自己的验证的地方   水平。

请注意,两个内联表单集prefix用于具有给定值的formset表单字段名称,以允许将多个表单集发送到视图而不会发生名称冲突。