且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

避免在django中更新模型窗体的实例化

更新时间:2023-12-06 11:41:16

我记得有这个问题,没有找到一个内置或惯用的解决方案。我没有想出比这个装饰器更好的解决方案(是的,这是生产代码):

  def model_generated(model):
从模型字段生成表单字段

目的:
这个装饰器可以让您在形式字段属性如
`max_length`从模型字段复制到DRY

用法:
使用此装饰器装饰一个表单类,并将MODEL_GENERATED_FIELDS
设置为要生成的属性名称列表

限制:
- 目前,这个装饰器只支持CharFields


def decorator(cls):

在cls.MODEL_GENERATED_FIELDS中的field_name:
model_field = model._meta.get_field(field_name)

model_field_type = type(model_field)
如果model_field_type == django_models.CharField:
form_field_type = forms.CharField
attributes = {#(form_attribute,model_attribute,processor)
('label',' verbose_name',None),
('max_length',None,None),
('min_length',None,None),
('required','blank'
}
else:
#(也许有一天这个装饰器会支持更多类型的字段)
raise ValueError(Unknown type of model field:{} formform(model_field_type))

kwargs = {}
for form_attribute,model_attribute,属性中的处理器:
如果model_attribute为无:
model_attribute = form_attribute
如果处理器为无:
处理器= lambda值:值

如果hasattr(model_field,model_attribute):
kwargs [form_attribute] = processor(getattr(model_field,model_attribute))

form_field = form_field_type(** kwargs)
setattr(cls,field_name,form_field)

#注册字段,因为我们是猴子修补
#(Django的meta-class hackery来检测字段
cls.base_fields [field_name] = form_field

return cls

返回装饰器

所以例如我会有:

  class Team(models.Model):
name = models.CharField(max_length = 30,unique = True,
verbose_name =Team Name)
passphrase = models.CharField max_length = 30,
verbose_name =密码)
...

和:

  @model_generated(models.Team)
class TeamJoiningForm(forms.Form):
MODEL_GENERATED_FIELDS =('name','passphrase')
...

您可以根据自己的具体需求调整并扩展 model_generated 装饰器。对不起。


I have a modelform with a custom modelchoicefield.

class UserModelChoiceField(forms.ModelChoiceField):
    def label_from_instance(self, obj):
         return obj.get_full_name()

class TaskForm(forms.ModelForm):
    originator = UserModelChoiceField(queryset=User.objects.all().order_by('first_name'),widget=forms.Select(attrs={'class':'form-control'}))

    class Meta:
        model = Task
        fields = ['originator']

My reading suggests that handling my field this way is overriding any info from the model (for example whether or not it is required) because it's instantiating it from my definition rather than augmenting the model's field.

It seems that to minimise my alteration of fields like this I should be interacting with the init instead (see below)

def __init__(self,*args, **kwargs):
    super(TaskForm, self).__init__(*args, **kwargs)

    self.fields['originator'].queryset=User.objects.all().order_by('first_name')
    self.fields['originator'].widget = forms.Select(attrs={'class':'form-control'})

I understand how to do the queryset and widget assuming that the above is correct. My question is how do I use that custom choicefield. Moreover I'm not even sure if this is the way to do it as it seems a little hacky.

Ugh.. I remember having this problem and not finding a built-in or idiomatic solution. I didn’t come up with a better solution than this decorator (and yes, this is production code):

def model_generated(model):
    """Generate form fields from model fields

    Purpose:
        This decorator lets you have form field attributes like
        `max_length` copied over from a model field while being DRY.

    Usage:
        Decorate a form class with this decorator and set MODEL_GENERATED_FIELDS
        to a list of attribute names you would like to be generated.

    Limitations:
        - Currently, this decorator only supports CharFields.
    """

    def decorator(cls):

        for field_name in cls.MODEL_GENERATED_FIELDS:
            model_field = model._meta.get_field(field_name)

            model_field_type = type(model_field)
            if model_field_type == django_models.CharField:
                form_field_type = forms.CharField
                attributes = {  # (form_attribute, model_attribute, processor)
                    ('label', 'verbose_name', None),
                    ('max_length', None, None),
                    ('min_length', None, None),
                    ('required', 'blank', lambda value: not value),
                }
            else:
                # (Maybe one day this decorator will support more types of fields.)
                raise ValueError("Unknown type of model field: {}".format(model_field_type))

            kwargs = {}
            for form_attribute, model_attribute, processor in attributes:
                if model_attribute is None:
                    model_attribute = form_attribute
                if processor is None:
                    processor = lambda value: value

                if hasattr(model_field, model_attribute):
                    kwargs[form_attribute] = processor(getattr(model_field, model_attribute))

            form_field = form_field_type(**kwargs)
            setattr(cls, field_name, form_field)

            # Register field since we're monkey-patching
            # (Django's meta-class hackery to detect fields only runs the first time the class is declared.)
            cls.base_fields[field_name] = form_field

        return cls

    return decorator

So for example I would have:

 class Team(models.Model):
    name = models.CharField(max_length=30, unique=True,
                            verbose_name="Team Name")
    passphrase = models.CharField(max_length=30,
                                  verbose_name="Passphrase")
    ...

and:

@model_generated(models.Team)
class TeamJoiningForm(forms.Form):
    MODEL_GENERATED_FIELDS = ('name', 'passphrase')
    ...

You might adapt and extend the model_generated decorator for your own specific needs. Sorry.