from django.db import models class Fieldset(object): """ Fieldset objects can be declared within model declarations to allow fields to be automatically grouped into fieldsets. django.db.models.Field.creation_counter is used to preserve the order of fields. When Python 3 is used, ordered attr dictionaries passed to metaclass __new__ methods would eliminate this requirement. Simply declare a new Fieldset object between field declaration in your model, and all following fields are automatically included in it. Any fields preceeding Fieldset declarations are grouped into a plain fieldset. Fieldset objects take an optional name argument, and a number of keyword arguments. 'classes' and 'description' correspond to the dictionary values in a django fieldset. 'grouped' is a boolean shortcut argument for enclosing all fields in the fieldset within a tuple. Subsets of fields can be grouped by calling the Fieldset.new_group(n) static method, which causes the following n fields to be grouped using a tuple (appear in one row of a django form). """ fieldsets = {} groups = {} def __init__(self, name=None, **kwargs): self.name = name self.classes = kwargs.get('classes', None) self.description = kwargs.get('description', None) self.fields = [] self.last = None self.grouped = kwargs.get('grouped', False) if kwargs.get('dummy'): self.first = None else: self.first = models.Field.creation_counter; Fieldset.fieldsets[None] = Fieldset.fieldsets.get(None, [])+[self] def __contains(self,field): # will raise if self.first == self.last == None if self.last == None: return (field.creation_counter >= self.first) if self.first == None: return (field.creation_counter <= self.last) return (field.creation_counter in range(self.first, self.last+1)) def get_fieldset(self): d = {'fields' : self.fields} if self.grouped: d['fields'] = (tuple(d['fields']),) if self.classes: d['classes'] = self.classes if self.description: d['description'] = self.description return (self.name, d) @staticmethod def new_group(n): Fieldset.groups[models.Field.creation_counter] = n @staticmethod def which_group(field): for g in Fieldset.groups: if field.creation_counter in range(g,g+Fieldset.groups[g]): return g return None @staticmethod def claim_fieldsets(model): unclaimed = [Fieldset(dummy=True)] + Fieldset.fieldsets.pop(None, []) for (i,f) in enumerate(unclaimed[:-1]): f.last = unclaimed[i+1].first - 1 Fieldset.fieldsets[model] = unclaimed @staticmethod def get_fieldsets(model): fieldsets = Fieldset.fieldsets.get(model.__name__, []) for f in fieldsets: group = [] group_id = None for field in model._meta.fields: if field.editable and not field.auto_created: if f.__contains(field): g = Fieldset.which_group(field) if g: # field is part of a group if g!=group_id and group!=[]: # new adjacent group # flush old one first: f.fields.append(tuple(group)) group = [] group.append(field.name) group_id = g else: if group != []: # must have just finished a group f.fields.append(tuple(group)) # append it first group = [] f.fields.append(field.name) # then the next field if group != []: f.fields.append(tuple(group)) # append any trailing group return [f.get_fieldset() for f in fieldsets if f.fields] class MyBase(models.base.ModelBase): def __new__(cls, name, bases, attrs): Fieldset.claim_fieldsets(name) return super(MyBase, cls).__new__(cls, name, bases, attrs) class ModelWithFieldsets(models.Model): __metaclass__ = MyBase class Meta: abstract = True