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. Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 6 months ago

Comments

Please login first before commenting.