Django : presenting a form very different from the model and with multiple field values in a Django-ish way?
I'm currently doing a firewall management application for Django, here's the (simplified) model :
class Port(models.Model):
number = models.PositiveIntegerField(primary_key=True)
application = models.CharField(max_length=16, blank=True)
class Rule(models.Model):
port = models.ForeignKey(Port)
ip_source = models.IPAddressField()
ip_mask = models.IntegerField(validators=[MaxValueValidator(32)])
machine = models.ForeignKey("vmm.machine")
What I would like to do, however, is to display to the user a form for entering rules, but with a very different organization than the model :
Port 80
O Not open
O Everywhere
O Specific addresses :
--------- delete field
--------- delete field
+
add address field
Port 443
... etc
Where Not open
means that there is no rule for the given port, Everywhere
means that there is only ONE rule (0.0.0.0/0) for the given port, and with specific addresses
, you can add as many addresses as you want (I did this with JQuery), which will make as many rules.
Now I did a version completely "handmade", meaning that I create the forms entirely in my templates, set input names wit开发者_开发知识库h a prefix, and parse all the POSTed stuff in my view (which is quite painful, and means that there's no point in using a web framework).
I also have a class which aggregates the rules together to easily pre-fill the forms with the informations "not open, everywhere, ...". I'm passing a list of those to the template, therefore it acts as an interface between my model and my "handmade" form :
class MachinePort(object):
def __init__(self, machine, port):
self.machine = machine
self.port = port
@property
def fully_open(self):
for rule in self.port.rule_set.filter(machine=self.machine):
if ipaddr.IPv4Network("%s/%s" % (rule.ip_source, rule.ip_mask)) == ipaddr.IPv4Network("0.0.0.0/0"):
return True
else :
return False
@property
def partly_open(self):
return bool(self.port.rule_set.filter(machine=self.machine)) and not self.fully_open
@property
def not_open(self):
return not self.partly_open and not self.fully_open
But all this is rather ugly ! Do anyone of you know if there is a classy way to do this ? In particular with the form... I don't know how to have a form that can have an undefined number of fields, neither how to transform these fields into Rule
objects (because all the rule fields would have to be gathered from the form), neither how to save multiple objects... Well I could try to hack into the Form class, but seems like too much work for such a special case. Is there any nice feature I'm missing ?
You can create usual Forms objects by subclassing Form
and adding fields in constructor, as in:
self.base_fields[field_name] = field_instance
As for the Rule
, You can create a custom Field
that will validate()
itself according to Your rules and add it to Your custom form as above.
So Yes, it must be handmande (AFAIK), but it's not so much code.
Ok, finally I got it running by making the models closer to what I wanted to present to the user. But related to the topic of the question :
1) Nested forms/formsets are not a built-in Django feature, are a pain to implement by yourself, and are actually not needed... Rather, one should use forms' and formsets' prefixes.
2) Trying to work with forms not based on the models, process the data, then reinject it in the models, is much much more code than modifying the models a little bit to have nice model-based forms. So what I did is I modified the models like that :
class PortConfig(Serializable):
port = models.ForeignKey(Port, editable=False)
machine = models.ForeignKey("vmm.machine", editable=False)
is_open = models.CharField(max_length=16, default="not_open", choices=is_open_choices)
class Rule(Serializable):
ip_source = models.CharField(max_length=24)
port_config = models.ForeignKey(PortConfig)
Then I simply used a "model formset" for PortConfig
, and "model inline formset" for Rule
, with a PortConfig
as foreign key, and it went perfectly
3) I used this great JS library http://code.google.com/p/django-dynamic-formset/ to put the "add field" and "delete field" links ... you almost have nothing to do.
精彩评论