Search code examples
pythondjangodjango-formsmulti-tenant

Django forms with variable "required" fields


I have a multi-tenant app where each "tenant" (Company model object) have multiple clients. Each tenant may set up which required fields they need for their app.

class Client(models.Model):
    """
    Client information
    """
    company = models.ForeignKey(Company, blank=True, null=True, default=1, on_delete=models.SET_NULL)
    name = models.CharField(max_length=150, blank=True)
    email = models.EmailField(max_length=255, unique=True)
    phone_number = models.CharField(max_length=50, blank=True, null=True)

class RequiredClientFields(models.Model):
    """
    Let each business decide how to enforce the data filling requirements for its staff/clients.
    0 - dont even show it
    1 - show it, but dont require (default)
    2 - require field for either staff or client
    3 - require for clients when self-filling their form, but not the staff members
    """
    company = models.ForeignKey(Company, db_index=True, on_delete=models.CASCADE)
    field_name = models.CharField(max_length=50)
    status = models.PositiveSmallIntegerField(choices=FIELD_STATUS_CHOICES, default=1)

So, when creating the django forms to use on the template, whats to best way to display (and validate) each field according to the Company's policies?

thanks


Solution

  • Something like this might work.

    Declare a ModelForm and overwrite __init__() with logic to delete fields or change their required status:

    class ClientForm(forms.ModelForm):
        class Meta:
            model = Client
            fields = '__all__'
    
        def __init__(self, company, user, *args, **kwargs):
            super(ClientForm, self).__init__(*args, **kwargs)
            # For each RequiredClientFields instance, 
            # get the matching form field and make changes
            for rule in RequiredClientFields.objects.filter(company=company):
                # get the form field
                field = self.fields.get(rule.field_name, None)
                if field:
                    if rule.status == 0:
                        # remove field from form
                        self.fields.pop(rule.field_name)
                    elif rule.status == 2:
                        # require field
                        field.required = True
                    elif rule.status == 3 and not user.is_staff:
                        # require for clients when self-filling their form, 
                        # but not the staff members
                        field.required = True
    

    Then create an instance of the ModelForm in your view, passing arguments for the company and user.

    company = Company.objects.get(pk=1)
    client_form = ClientForm(company=company, user=request.user)
    

    Be aware that there are potential security implications when controlling form fields this way. Take a look at the relevant section in the Django ModelForm docs.