Pull to refresh

Динамическое создание форм на основе данных из базы в Django

Reading time3 min
Views9.7K
Началось всё с того что надо было часто создавать однотипные формы и сохранять введённые данные в базу. По суте форма всегда одна и та же — «Заявка на регистрацию» — но в зависимости от мероприятия поля в ней разные.

Обязанности подготовки формы надо было переложить на администратора сайта, поэтому было принято решение создать механизм управления формами через админский интерфейс Django. Так появилось приложение CDBForms.

Идея очень простая — создать модели для описания форм и хранения введённых данных. Соответственно появились модели Template (шалон формы), TemplateField (поле в шаблоне формы) и FieldParameter (параметры поля) для хранения информации о формах (шаблоны форм) и модели Record и RecordData для хранения данных, введённых через формы.

Собственно:
class Template(models.Model):
    tag = models.SlugField(max_length=16, unique=True)
    title = models.CharField(max_length=50)
    
    def fields(self):
        return TemplateField.objects.filter(template=self).order_by('tab')

    def __unicode__(self):
        return self.title

class TemplateField(models.Model):
    FieldTypes = (('T', 'Text'), ('B', 'Bool'), ('E', 'E-mail'), ('U', 'URL'), ('C', 'Choices'),)
    template = models.ForeignKey(Template)
    tag = models.SlugField(max_length=32)
    title = models.CharField(max_length=50)
    type = models.CharField(max_length=1, choices=FieldTypes)
    tab = models.IntegerField(default=0)
    required = models.BooleanField(default=True)

    def __unicode__(self):
        return u'%s: %s' % (self.template, self.tag)

    def parameters(self):
        return FieldParameter.objects.filter(field=self).order_by('tab')
        
    class Meta:
        unique_together = ("template", "tag")
        
class FieldParameter(models.Model):
    field = models.ForeignKey(TemplateField)
    tag = models.SlugField(max_length=32)
    value = models.CharField(max_length=255)
    tab = models.IntegerField(default=0)

    def __unicode__(self):
        return u'%s: %s = %s' % (self.field, self.tag, self.value)
        
    class Meta:
        unique_together = ("field", "tag", "value")

class Record(models.Model):
    template = models.ForeignKey(Template)
    dt = models.DateTimeField()
    
    def __unicode__(self):
        return u'%s @ %s' % (self.template, self.dt)

class RecordData(models.Model):
    record = models.ForeignKey(Record)
    field = models.ForeignKey(TemplateField)
    value = models.CharField(max_length=255)
    
    def __unicode__(self):
        return u'%s %s' % (self.record, self.field)
        
    class Meta:
        unique_together = ("record", "field")


Дальше осталось только реализовать класс нашей формы:
class CDBForm(forms.Form):
    def __init__(self, template=None, tag=None, *args, **kwargs):
        if template:
            self.template = template
        elif tag:
            self.template = Template.objects.get(tag=tag)
        fields = self.template.fields()
        for field in fields:
            if field.type == "B":
                self.base_fields[field.tag] = forms.BooleanField(label=field.title, required=field.required)
            elif field.type == "T":
                self.base_fields[field.tag] = forms.CharField(label=field.title, required=field.required)
            elif field.type == "C":
                choices = []
                for parameter in field.parameters():
                    if parameter.tag == "choice":
                        choices.append((parameter.value, parameter.value))
                self.base_fields[field.tag] = forms.ChoiceField(choices=choices, label=field.title, required=field.required)
            elif field.type == "E":
                self.base_fields[field.tag] = forms.EmailField(label=field.title, required=field.required)
            elif field.type == "U":
                self.base_fields[field.tag] = forms.URLField(label=field.title, required=field.required)
        forms.Form.__init__(self, *args, **kwargs)

    def save(self, commit=True):
        data = self.cleaned_data
        if commit:
            rec = Record(template=self.template, dt=datetime.now())
            rec.save()
            for k,v in data.iteritems():
                f = TemplateField.objects.get(template=self.template, tag=k)
                d = RecordData(record=rec, field=f, value=v)
                d.save()
        return data


Форма готова к использованию, осталось только реализовать удобный интерфейс администратора. Пример как это работает можно посмотреть тут и тут.

Скачать код можно с code.google.com/p/django-cdbforms
Кстати, если кому-то интересно, то буду рад сотрудничеству! Спасибо!
Tags:
Hubs:
+12
Comments9

Articles