Reputation: 730
I have a Sandbox which contains plenty of items composed of one or more attributes. Each attribute belongs to a particular attribute type (such as color, shape, etc) I am at loss on how to render the form with Attributes grouped together with their AttributeTypes.
Models
class Sandbox(models.Model):
item = models.ForeignKey('Item')
class Item(models.Model):
name = models.CharField()
sandbox = models.ForeignKey(Sandbox)
attributes = models.ManyToManyField('Attribute')
class Attribute(models.Model):
name = models.CharField()
type = models.ForeignKey('AttributeType')
class AttributeType(models.Model):
name = models.CharField()
class ItemAttribute(models.Model):
# intermediary model to hold references
item = models.ForeignKey(Item)
type = models.ForeignKey(AttributeType)
attribute = models.ForeignKey(Attribute)
ModelForm
class Sandbox(ModelForm):
class Meta:
model = Sandbox
There can only be one choice per attribute type. For instance, something can only have one color or one shape.
AttributeType Attribute Users choice
color
red
blue [x]
green
shape
shape
triangular
squared [x]
spherical
This is were I get stuck. How do I group these attributes together in the form and how can I use radio buttons to select only one attribute per type? Perhaps my original idea of having a simple model representation falls short here? I have tried documentation, StackOverflow and Google but no luck.
Any tips and ideas are welcome.
My solution
I built up a solution which satisfies my needs. @bmihelac pointed me in a good direction to this article on how to create a factory method for creating a custom form. [see below]
def make_sandbox_form(item):
def get_attributes(item):
item_attributes = ItemAttribute.objects.filter(item=item)
# we want the first attribute type
_attr_type = item_attributes[0].attribute.all()[0].attribute_type
choices = [] # holds the final choices
attr_fields = {} # to hold the final list of fields
for item_attrs in item_attributes.all():
attributes = item_attrs.attribute.all().order_by('attribute_type')
for attr in attributes:
print attr.attribute_type, ' - ' , _attr_type
if attr.attribute_type == _attr_type:
choices.append( ( attr.pk, attr.value ) )
else:
d = {u'%s' % _attr_type : fields.ChoiceField(choices=choices, widget=RadioSelect)}
attr_fields = dict(attr_fields.items() + d.items() )
# set the _attr_type to new type and start over with next attribute type
_attr_type = attr.attribute_type
choices = []
return attr_fields
form_fields = {
'item' : fields.IntegerField(widget=HiddenInput),
}
form_fields = dict(form_fields.items() + get_attributes(item).items())
return type('SandboxForm', (forms.BaseForm, ), { 'base_fields' : form_fields})
Invoke the form my calling this factory method as: form = make_sandbox_form()
http://www.b-list.org/weblog/2008/nov/09/dynamic-forms/
(Wish I could upvote all but being a StackOverflow rookie I lack the reputation to do so.)
Upvotes: 1
Views: 310
Reputation: 6323
I would create dynamic form, creating one choice field for every AttributeType.
Then, you can easily replace widget with radio buttons.
This article could be helpful:
http://www.b-list.org/weblog/2008/nov/09/dynamic-forms/
Upvotes: 1
Reputation: 4656
Can one sandbox have many items, or one item have many sandboxes? Can an item belong to multiple sandboxes at once?
I think you want one sandbox to contain many items:
class Sandbox(models.Model):
name = models.CharField()
class Item(models.Model):
name = models.CharField()
sandbox= models.ForeignKey(Sandbox)
attributes = models.ManyToManyField('Attribute')
The same analysis holds true here:
Does one attribute have many attribute types, or one attribute type have many attributes?
Here you have the relationship right, I just switched the order of the models
class AttributeType(models.Model):
name = models.CharField()
class Attribute(models.Model):
name = models.CharField()
type = models.ForeignKey(AttributeType)
So each item has an attribute and they always are endowed with these attributes, color and shape.
While you could have a table that had data that looked like:
pk type
1 green
2 circular
etc
I personally wouldn't do that, because I think data that is logically the same should be grouped together. Shapes have different attributes than colors. For example and to illustrate my point, what if you wanted the RGB of a color? Then you would have extra columns for shapes when they aren't needed and this is confusing. Same is true in the converse, colors don't have dimension.
Instead, I might do something like:
class Color(models.Mode):
#info about colors
class Shape(models.Mode):
#info about shapes
class Item(models.Model):
name = models.CharField()
sandbox= models.ForeignKey(Sandbox)
color= models.ForeignKey(Color)
shape= models.ForeignKey(Shape)
This also guarantees you only have one choice per, because ForeignKey in django.Forms defaults you to a ChioceField (iirc).
As for overriding that and making it a radiobutton, just review the docs here:
https://docs.djangoproject.com/en/dev/ref/forms/widgets/
Upvotes: 1