我正在执行一项具有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提及并使用前缀。我刚刚克服了一个使用prefix
和auto_id
参数很好解决的问题。
看起来您的表单设计很复杂,因此您可能会遇到一些问题,并且由于其中有许多活动部件,因此很难弄清楚哪个问题。