Django: allow user to add fields to model

2020-08-01 02:01发布

问题:

I am just starting with Django and want to create a model for an application.

I find Djangos feature to - automatically define validations and html widget types for forms according to the field type defined in the model and - define a choice set for the field right in the model very usefull and I want to make best use of it. Also, I want to make best use of the admin interface.

However, what if I want to allow the user of the application to add fields to the model? For example, consider a simple adress book. I want the user to be able to define additional atributes for all of his contacts in the admin settings, i.e. add a fax number field, so that a fax number can be added to all contacts.

from a relational DB perspective, I would have a table with atributes (PK: atr_ID, atr_name, atr_type) and an N:N relation between atributes and contacts with foreign keys from atributes and contacts - i.e. it would result in 3 tables in the DB. right?

but that way I cannot define the field types directly in the Django model. Now what is best practice here? How can I make use of Djangos functionality AND allow the user to add aditional/custom fields via the admin interface?

Thank you! :)

Best Teconomix

回答1:

I've used this approach, first seen in django-payslip, to allow for extendable fields. This provides a structure for adding fields to models, from which you can allow users to add/edit through standard view procedures (no admin hacking necessary). This should be enough to get you started, and taking a look at django-payslip's source code (see the views) also provides view Mixins and forms as an example of how to render to users.

class YourModel(models.Model):
    extra_fields = models.ManyToManyField(
        'your_app.ExtraField',
         verbose_name=_('Extra fields'),
         blank=True, null=True,
    )


class ExtraFieldType(models.Model):
    """
    Model to create custom information holders.
    :name: Name of the attribute.
    :description: Description of the attribute.
    :model: Can be set in order to allow the use of only one model.
    :fixed_values: Can transform related exta fields into choices.
    """
    name = models.CharField(
        max_length=100,
        verbose_name=_('Name'),
    )

    description = models.CharField(
        max_length=100,
        blank=True, null=True,
        verbose_name=_('Description'),
    )

    model = models.CharField(
        max_length=10,
        choices=(
            ('YourModel', 'YourModel'),
            ('AnotherModel', 'AnotherModel'), # which models do you want to add extra fields to?
        ),
        verbose_name=_('Model'),
        blank=True, null=True,
    )

    fixed_values = models.BooleanField(
        default=False,
        verbose_name=_('Fixed values'),
    )

    class Meta:
        ordering = ['name', ]

    def __unicode__(self):
        return '{0}'.format(self.name)



class ExtraField(models.Model):
    """
    Model to create custom fields.
    :field_type: Connection to the field type.
    :value: Current value of this extra field.
    """
    field_type = models.ForeignKey(
        'your_app.ExtraFieldType',
        verbose_name=_('Field type'),
        related_name='extra_fields',
        help_text=_('Only field types with fixed values can be chosen to add'
                    ' global values.'),
    )

    value = models.CharField(
        max_length=200,
        verbose_name=_('Value'),
    )

    class Meta:
        ordering = ['field_type__name', ]

    def __unicode__(self):
        return '{0} ({1}) - {2}'.format(
            self.field_type, self.field_type.get_model_display() or 'general',
            self.value)


回答2:

i would suggest storing json as a string in the database, that way it can be as extendable as you want and the field list can go very long.

Edit: If you are using other damn backends you can use Django-jsonfield. If you are using Postgres then it has a native jsonfield support for enhanced querying, etc.

Edit 2: Using django mongodb connector can also help.



回答3:

You can use InlineModelAdmin objects. It should be something like:

#models.py
from django.db import models

class Person(models.Model):
   name = models.CharField(max_length=100)

class ContactType(models.Model):
   name = models.CharField(max_length=100)

class Contact(models.Model):
   person = models.ForeignKey(Person, on_delete=models.CASCADE)
   contact_type = models.ForeignKey(ContactType, on_delete=models.CASCADE)
   value = models.CharField(max_length=100)

#admin.py
from django.contrib import admin

class ContactInline(admin.TabularInline):
    model = Contact

class PersonAdmin(admin.ModelAdmin):
    inlines = [
        ContactInline,
    ]

By the way... stackoverflow questions should contain some code. You should try to do something before asking a question.