Login

DRY Fieldsets

Author:
DrMeers
Posted:
June 11, 2009
Language:
Python
Version:
1.0
Score:
2 (after 2 ratings)

I've devised a DRY method of declaring django fieldsets:

Example usage:

  1. Include the attached code in fieldsets.py

  2. models.py:

    from django.db import models
    from fieldsets import Fieldset, ModelWithFieldsets
    
    class Person(ModelWithFieldsets): #instead of models.Model
        # this field will be placed in nameless fieldset
        example_field = models.IntegerField() 
    
        # this fieldset will be grouped into one row
        Fieldset(grouped=True) 
        first_name = models.CharField(max_length=64)
        surname = models.CharField(max_length=64)
    
        Fieldset("Contact Details",  classes=('collapse',)) 
        mobile_phone = models.CharField(max_length=10)
        email_address = models.EmailField()
    
        Fieldset("Address")
        street_address = models.CharField(max_length=255)
        # the next two fields will be grouped into one row of this fieldset
        Fieldset.new_group(2) 
        suburb = models.CharField(max_length=64)
        state = models.CharField(max_length=64)
    
  3. admin.py:

    from django.contrib import admin
    from models import Person
    from fieldsets import Fieldset
    
    class PersonAdmin(admin.ModelAdmin):
        fieldsets = Fieldset.get_fieldsets(Person)
    
    admin.site.register(Person, PersonAdmin)
    

This example produces the equivalent of manually typing:

fieldsets = (
    (None, {'fields': ('example_field')}), 
    (None, {'fields': (('first_name', 'surname'),)}), 
    ('Contact Details', {
            'fields': ('mobile_phone', 'email_address'), 
            'classes': ('collapse',)}), 
    ('Address', {'fields': ('street_address', ('suburb', 'state'))})
)

But now if you want to rearrange your fields, rename, delete, insert, etc, you won't need to remember to update the fieldsets in the ModelAdmin.

This implementation is a bit of a hack, but I believe a cleaner equivalent should be implemented in django itself.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
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

More like this

  1. csrf test by zukosdead 3 weeks, 5 days ago
  2. find even number by Rajeev529 9 months, 1 week ago
  3. Form field with fixed value by roam 10 months ago
  4. New Snippet! by Antoliny0919 10 months ago
  5. Add Toggle Switch Widget to Django Forms by OgliariNatan 1 year ago

Comments

Please login first before commenting.