我正在执行一项具有5种形式的任务。
其中两种是表格,其他是使用modelformset_factory生成的。

背景:

我需要构建一个处理以下5种形式的视图:

CdnUrl
|-- Http
    |-- Location
        | -- HttpRedirect
        | -- HttpProxy
    |-- Location
        | -- HttpRedirect
        | -- HttpProxy
    |-- Location
        | -- HttpRedirect
        | -- HttpProxy


不能同时插入HttpProxy和HttpRedirect。只是HttpRedirect或HttpProxy,不能同时使用两者。

表格

class OneRequiredFormSet(BaseFormSet):
    def __init__(self, *args, **kwargs):
        super(OneRequiredFormSet, self).__init__(*args, **kwargs)
        for form in self.forms:
            form.empty_permitted = False


class CustomLocationFormset(OneRequiredFormSet):
    def __init__(self, *args, **kwargs):
        super(CustomLocationFormset, self).__init__(*args, **kwargs)
        self.forms[0].fields["uri"].initial = "/"
        self.forms[0].fields["uri"].widget.attrs["readonly"] = True

    def clean(self):
        super(CustomLocationFormset, self).clean()
        if self.forms[0].cleaned_data["uri"] != "/":
            raise forms.ValidationError(u"Your first URI must be '/'")


class CustomInlineStreamingFormset(BaseFormSet):
    pass


class CdnUrlForm(forms.ModelForm):
    class Meta:
        model = CdnUrl
        fields = ("account", )

    def __init__(self, *args, **kwargs):
        self.accounts_range = kwargs.pop("accounts_range", None)

        super(CdnUrlForm, self).__init__(*args, **kwargs)

        if self.accounts_range:
            self.fields["account"].queryset = self.accounts_range

    def save(self, commit=True, **kwargs):
        service = kwargs.pop("service", None)
        instance = super(CdnUrlForm, self).save(commit=False)
        if service:
            instance.service = service
        if commit:
            instance.save()
        return instance


class HttpForm(forms.ModelForm):
    CONFIGURATION_STATE_CHOICES = [
        (True, "Enabled"),
        (False, "Disabled")
    ]

    GZIP_CHOICES = [
        (True, "On"),
        (False, "Off"),
    ]

    configuration_state = forms.TypedChoiceField(choices=CONFIGURATION_STATE_CHOICES,
                                                 widget=forms.RadioSelect,
                                                 initial=True)
    gzip = forms.TypedChoiceField(choices=GZIP_CHOICES,
                                  widget=forms.RadioSelect,
                                  initial=False)

    class Meta:
        model = Http
        fields = ("cdnurl_allow_content_access",
                  "configuration_state",
                  "protocol_policy",
                  "ssl_certificate",
                  "gzip",
                  "cname_url",
                  "origin",
                  "host_header",
                  # admin fields
                  "manual_configuration",
                  "minimum_object_lifetime",
                  "connect_timeout",
                  "read_timeout",
                  "granularity_file_size", )


class LocationForm(forms.ModelForm):
    class Meta:
        model = Location
        fields = ("uri", )


class HttpRedirectForm(forms.ModelForm):
    class Meta:
        model = HttpRedirect
        fields = ("domain_redirect", )


class HttpProxyForm(forms.ModelForm):

    CACHE_QUERY_STRING_CHOICES = [
        (True, "Yes"),
        (False, "No (Improves Caching)"),
    ]

    cache_query_string = forms.TypedChoiceField(choices=CACHE_QUERY_STRING_CHOICES,
                                                widget=forms.RadioSelect,
                                                initial=False)

    class Meta:
        model = HttpProxy
        fields = ("end_user_caching",
                  "expires_range",
                  "expires_value",
                  "cdn_object_caching",
                  "cdn_object_lifetime",
                  "allowed_http_methods",
                  "forward_cookie",
                  "white_list_cookie",
                  "cache_query_string",
                  "proxy_headers",
                  "remove_headers",
                  "comments", )

    def clean(self):
        cleaned_data = super(HttpProxyForm, self).clean()
        expires_range = cleaned_data.get("expires_range")
        expires_value = cleaned_data.get("expires_value")

        if expires_range and expires_range.upper() == "Y" and expires_value > 10:
            raise forms.ValidationError(u"Invalid Range (Max is 10 Years)")

        return cleaned_data


所以我最初的想法是我必须有2个表单和3个modelformset。

CdnUrlForm和HttpForm是我的表单。
LocationForm,HttpRedirectForm和HttpProxyForm将用于构建我的表单集。

我这样做是因为要保存一致的记录,我需要:
-CdnUrl数据
-Http数据
-位置数据
-HttpProxy或HttpRedirect数据

我的设计是:

CdnUrlForm(仅一种形式)
HttpForm(仅一种形式)
位置(我可以在每页上插入几个位置)
HttpProxy(每个位置只能插入一个HttpProxy)
HttpRedirect(每个位置只能插入一个HttpRedirect)

这是我的看法:

views.py

@reversion.create_revision()
@login_required
@user_passes_test(lambda u: u.is_active)
def create_configuration(request, service_type, service_code):
    contact_logged = request.user.get_profile()
    accounts_range = contact_logged.account.get_self_and_children()

    contact_services = get_contact_services(request)

    service = Services.objects.get(url_name=service_code)

    cdn_url_form = CdnUrlForm(request.POST or None, accounts_range=accounts_range)

    http_form = HttpForm(request.POST or None)

    LocationFormSet = modelformset_factory(Location, form=LocationForm, formset=CustomLocationFormset, extra=2,
                                           can_delete=False)
    location_form_set = LocationFormSet(request.POST or None)

    RedirectFormSet = modelformset_factory(HttpRedirect, form=HttpRedirectForm, can_delete=False, extra=2)

    ProxyFormSet = modelformset_factory(HttpProxy, form=HttpProxyForm, can_delete=False, extra=2)

    redirect_form_set = RedirectFormSet(request.POST or None, queryset=HttpRedirect.objects.none())
    proxy_form_set = ProxyFormSet(request.POST or None, queryset=HttpProxy.objects.none())

    if request.method == "POST":
        if cdn_url_form.is_valid():
            cdn_url_instance = cdn_url_form.save(commit=False, service=service)

            if http_form.is_valid() and location_form_set.is_valid():
                http_instance = http_form.save(commit=False)

                for location_form in location_form_set:
                    location_instance = location_form.save(commit=False)

                    if redirect_form_set.is_valid():
                        for redirect_form in redirect_form_set:
                            cdn_url_instance.save()
                            redirect_instance = redirect_form.save(commit=False)
                            http_instance.cdn_url = cdn_url_instance
                            http_instance.save()
                            location_instance.http = http_instance
                            location_instance.save()
                            redirect_instance.location = location_instance
                            redirect_instance.save()

                    if proxy_form_set.is_valid():
                        for proxy_form in redirect_form_set:
                            cdn_url_instance.save()
                            proxy_instance = proxy_form.save(commit=False)
                            http_instance.cdn_url = cdn_url_instance
                            http_instance.save()
                            location_instance.http = http_instance
                            location_instance.save()
                            proxy_instance.location = location_instance
                            proxy_instance.save()

                return HttpResponseRedirect(reverse("dashboard"))

    return render_to_response(
        "cdnsetup/configuration/create_or_edit.html",
        {"contact_services": contact_services,
         "service_type": service_type,
         "cdn_url_form": cdn_url_form,
         "http_form": http_form,
         "location_form_set": location_form_set,
         "redirect_form_set": redirect_form_set,
         "proxy_form_set": proxy_form_set},
        RequestContext(request),
    )


每当我将数据发布到此视图时,似乎都无法验证我的modelformset。当然,我的代码是错误的,但是我不知道如何处理所有这些形式来执行以下操作:

从CdnUrlForm插入数据
从HttpForm插入数据
插入所有位置

对于每个位置,请检查HttpRedirectForm实例是否有效。如果它们有效,则只能插入HttpRedirectForm数据,而不是HttpProxy(因此,我将忽略HttpProxyForm中的任何数据)。

这是我的模型:

models.py

import uuid
from datetime import date
from django.conf import settings
from django.db import models
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from azionmanager.validators.urls_validator import validate_domain


class CdnUrl(models.Model):
    cdn_url = models.CharField(_("cdn url"), max_length=255, null=False, blank=False, unique=True)
    account = models.ForeignKey("controlpanel.Accounts", null=False, blank=False)
    # related service
    service = models.ForeignKey("controlpanel.Services", null=False, blank=False)
    # sequential field used to create cdn url field
    sequential = models.IntegerField()

    @classmethod
    def get_next_sequence(cls, account, service):
        """Get the next sequential number if CDN URL does not exist yet."""
        sequential = cls.objects.filter(account=account, service=service).defer("sequential").order_by("-sequential")
        if not sequential:
            return 1
        return sequential[0].sequential + 1

    def create_cdn_url(self):
        self.cdn_url = "{sequential}{account}.{service}.{domain}". \
            format(sequential=self.sequential, account=self.account.client_id, service=self.service.url_name,
                   domain=settings.CDN_DOMAIN)

    def get_absolute_url(self):
        return reverse("cdnsetup.views.services_setup.edit_configuration",
                       args=[self.service.service_type.lower(),
                             self.service.url_name,
                             self.id])

    def save(self, *args, **kwargs):
        self.sequential = CdnUrl.get_next_sequence(self.account, self.service)
        self.create_cdn_url()
        super(CdnUrl, self).save(*args, **kwargs)


class Http(models.Model):
    STATUS_CODES_CHOICES = [
        ("P", "Pending"),
        ("V", "Valid"),
        ("I", "Invalid"),
        ("L", "Locked")
    ]

    PROTOCOL_CHOICES = [
        ("HTTP", "HTTP"),
        ("HTTPS", "HTTPS"),
        ("HTTP+HTTPS", "HTTP & HTTPS"),
    ]

    GRANULARITY_FILE_SIZE_CHOICES = [
        ("SF", "Small Files"),
        ("LF", "Large Files"),
    ]

    cdn_url = models.ForeignKey(CdnUrl)

    # distribution settings

    cdnurl_allow_content_access = models.BooleanField(_("allow content access through CDN url"), default=True)
    configuration_state = models.BooleanField(_("configuration state"), default=True)
    protocol_policy = models.CharField(_("viewer protocol policy"), max_length=10, choices=PROTOCOL_CHOICES, null=False,
                                       blank=False,
                                       default=PROTOCOL_CHOICES[0][0])
    ssl_certificate = models.CharField(_("ssl certificate"), max_length=255, null=True, blank=True)
    gzip = models.BooleanField(_("gzip content"), default=False)

    cname_url = models.TextField(_("cname url"), max_length=255, null=False, blank=False)

    # origin settings

    origin = models.CharField(_("origin domain name"), validators=[validate_domain], max_length=255, null=False,
                              blank=False)
    host_header = models.CharField(validators=[validate_domain], max_length=255, null=True, blank=True)

    # used after configuration is saved
    status = models.CharField(_("validation status"), max_length=1, choices=STATUS_CODES_CHOICES, editable=False,
                              default=STATUS_CODES_CHOICES[0][0])

    # admin settings

    manual_configuration = models.BooleanField(_("manual configuration"), default=False)
    minimum_object_lifetime = models.IntegerField(_("minimum object lifetime"), default=60, null=False, blank=True)
    connect_timeout = models.IntegerField(_("connect timeout"), default=60, null=True, blank=True)
    read_timeout = models.IntegerField(_("read timeout"), default=120, null=True, blank=True)
    granularity_file_size = models.CharField(_("granularity file size"), max_length=2,
                                             choices=GRANULARITY_FILE_SIZE_CHOICES,
                                             default=GRANULARITY_FILE_SIZE_CHOICES[1][0],
                                             null=False, blank=True)

    def __unicode__(self):
        return "<{cdn_url}>: {cname_url}".format(cdn_url=self.cdn_url, cname_url=self.cname_url)


class Location(models.Model):
    http = models.ForeignKey(Http)
    uri = models.CharField(max_length=255, null=False, blank=False)

    created_at = models.DateField(_("create date"), default=date.today)
    updated_at = models.DateField(_("update date"), auto_now_add=True)


class HttpRedirect(models.Model):
    location = models.ForeignKey(Location, related_name="redirect")

    domain_redirect = models.URLField(_("domain redirect"), max_length=255, null=False, blank=False)


class HttpProxy(models.Model):
    RANGE_UNITS = [
        ("S", "Seconds"),
        ("M", "Minutes"),
        ("H", "Hours"),
        ("D", "Days"),
        ("W", "Weeks"),
        ("M", "Months"),
        ("Y", "Years"),
    ]

    GRANULARITY_FILE_SIZE = [
        ("SF", "Small Files"),
        ("LF", "Large Files"),
    ]

    CACHING_CHOICES = [
        ("O", "Use Origin Cache Headers"),
        ("C", "Customize"),
    ]

    location = models.ForeignKey(Location, related_name="proxy")

    # cache settings

    end_user_caching = models.CharField(_("end user caching"), max_length=1, choices=CACHING_CHOICES,
                                        default=CACHING_CHOICES[0][0])
    expires_range = models.CharField(_("expires_range"), max_length=2, choices=RANGE_UNITS, default=RANGE_UNITS[3][0])
    expires_value = models.IntegerField(_("expires_value"), default=30)

    cdn_object_caching = models.CharField(_("cdn object caching"), max_length=1, choices=CACHING_CHOICES,
                                          default=CACHING_CHOICES[0][0])
    cdn_object_lifetime = models.IntegerField(_("cdn object lifetime"), default=10080, null=False, blank=False)

    allowed_http_methods = models.TextField(_("allowed http methods"), max_length=255, null=False, blank=False)
    forward_cookie = models.CharField(_("forward cookie"), max_length=255, null=True, blank=True)
    white_list_cookie = models.CharField(_("white list cookie"), max_length=255, null=True, blank=True)
    cache_query_string = models.BooleanField(_("cache query string"), default=False)

    proxy_headers = models.TextField(_("proxy headers"), null=True, blank=True)
    remove_headers = models.TextField(_("remove headers"), null=True, blank=True)

    # add comments to the configuration
    comments = models.TextField(null=True, blank=True)

    def clean(self):
        if self.cdn_object_lifetime < self.location.http.cdn_url.minimum_object_lifetime:
            raise ValidationError(u"Object lifetime can not be less than {minimum_object_lifetime}".format(
                minimum_object_lifetime=self.location.http.cdn_url.minimum_object_lifetime))

    def __unicode__(self):
        return "<{cdn_url}>: {uri}".format(cdn_url=self.cdn_url, uri=self.uri)


最后,如果它们没有附加的HttpProxy或HttpRedirect实例,我不希望插入Location实例。就像我之前说的,我每个页面可以有多个位置,并且HttpProxy或HttpRedirect可以为这些位置之一。
我需要怎么做才能在同一页面中处理这些表格?

最佳答案

几件事情我可能会尝试看看是否一切正常。首先,打开外壳并创建一些表单并将其绑定到数据并手动验证它们。通常,这将帮助您进一步进行调查。

接下来,每当使用多个表单和表单集时,请执行@ Anentropic提及并使用前缀。我刚刚克服了一个使用prefixauto_id参数很好解决的问题。

看起来您的表单设计很复杂,因此您可能会遇到一些问题,并且由于其中有许多活动部件,因此很难弄清楚哪个问题。

10-08 14:53