Vraag Creatie van dynamische modelvelden in django


Dit is een probleem met betrekking tot django. Ik heb een model met de tekst "Automobiles". Dit heeft een aantal basisvelden zoals "Kleur", "Voertuigbezitternaam", "Voertuigkosten".

Ik wil een formulier bieden waarin de gebruiker extra velden kan toevoegen, afhankelijk van de auto die hij toevoegt. Als de gebruiker bijvoorbeeld een 'Auto' toevoegt, krijgt hij extra velden in de vorm, dynamisch tijdens runtime, zoals 'Car Milage', 'Cal Manufacturer'. Stel dat de gebruiker een "Truck" wil toevoegen, dan voegt hij "Load that can be carried", "Permit" etc.

Hoe bereik ik dit in Django?

Er zijn twee vragen hier:

  1. Hoe een formulier te bieden waar de gebruiker tijdens runtime nieuwe velden kan toevoegen?
  2. Hoe de velden aan de database toe te voegen zodat deze later kan worden opgehaald / opgevraagd?

15
2017-09-14 20:35


oorsprong


antwoorden:


Er zijn een paar benaderingen:

  • sleutel / waarde model (eenvoudig, goed ondersteund)
  • JSON-gegevens in een TextField (eenvoudig, flexibel, kan niet gemakkelijk zoeken / indexeren)
  • Dynamische modeldefinitie (niet zo gemakkelijk, veel verborgen problemen)

Het klinkt alsof je de laatste wilt, maar ik weet niet zeker of dit het beste voor jou is. Django is heel gemakkelijk te veranderen / updaten, als systeembeheerders extra velden willen, voeg je ze gewoon toe en gebruik je zuid om te migreren. Ik hou niet van generieke sleutel / waarde database schema's, het hele punt van een krachtig framework zoals Django is dat je eenvoudig aangepaste schema's kunt schrijven en herschrijven zonder gebruik te maken van generieke benaderingen.

Als u sitegebruikers / -beheerders moet toestaan ​​hun gegevens direct te definiëren, weet ik zeker dat anderen u laten zien hoe u de eerste twee benaderingen hierboven kunt uitvoeren. De derde benadering is waar je om vroeg, en een beetje geker, ik zal je laten zien hoe te doen. Ik raad het niet aan om het in bijna alle gevallen te gebruiken, maar soms is het passend.

Dynamische modellen

Als u eenmaal weet wat u moet doen, is dit relatief eenvoudig. Je hebt nodig:

  • 1 of 2 modellen om de namen en typen van de velden op te slaan
  • (optioneel) Een abstract model om algemene functionaliteit voor uw (subklassen) dynamische modellen te definiëren
  • Een functie om het dynamische model te bouwen (of opnieuw te bouwen) wanneer dat nodig is
  • Code om de databasetabellen te bouwen of bij te werken wanneer velden worden toegevoegd / verwijderd / hernoemd

1. De modeldefinitie opslaan

Dit is aan jou. Ik stel me voor dat je een model hebt CustomCarModel en CustomField om de gebruiker / beheerder de namen en typen van de gewenste velden te laten definiëren en opslaan. U hoeft Django-velden niet rechtstreeks te spiegelen, u kunt uw eigen typen maken die de gebruiker wellicht beter begrijpt.

Gebruik een forms.ModelForm met inline formulieren om de gebruiker zijn eigen klasse te laten bouwen.

2. Abstract model

Nogmaals, dit is eenvoudig, maak gewoon een basismodel met de gemeenschappelijke velden / methoden voor al uw dynamische modellen. Maak dit model abstract.

3. Bouw een dynamisch model

Definieer een functie die de vereiste informatie (misschien een instantie van uw klas van # 1) neemt en een modelklasse produceert. Dit is een eenvoudig voorbeeld:

from django.db.models.loading import cache
from django.db import models


def get_custom_car_model(car_model_definition):
  """ Create a custom (dynamic) model class based on the given definition.
  """
  # What's the name of your app?
  _app_label = 'myapp'

  # you need to come up with a unique table name
  _db_table = 'dynamic_car_%d' % car_model_definition.pk

  # you need to come up with a unique model name (used in model caching)
  _model_name = "DynamicCar%d" % car_model_definition.pk

  # Remove any exist model definition from Django's cache
  try:
    del cache.app_models[_app_label][_model_name.lower()]
  except KeyError:
    pass

  # We'll build the class attributes here
  attrs = {}

  # Store a link to the definition for convenience
  attrs['car_model_definition'] = car_model_definition

  # Create the relevant meta information
  class Meta:
      app_label = _app_label
      db_table = _db_table
      managed = False
      verbose_name = 'Dynamic Car %s' % car_model_definition
      verbose_name_plural = 'Dynamic Cars for %s' % car_model_definition
      ordering = ('my_field',)
  attrs['__module__'] = 'path.to.your.apps.module'
  attrs['Meta'] = Meta

  # All of that was just getting the class ready, here is the magic
  # Build your model by adding django database Field subclasses to the attrs dict
  # What this looks like depends on how you store the users's definitions
  # For now, I'll just make them all CharFields
  for field in car_model_definition.fields.all():
    attrs[field.name] = models.CharField(max_length=50, db_index=True)

  # Create the new model class
  model_class = type(_model_name, (CustomCarModelBase,), attrs)

  return model_class

4. Code om de databasetabellen bij te werken

De bovenstaande code genereert een dynamisch model voor u, maar maakt de databasetabellen niet. Ik raad aan om Zuid te gebruiken voor tafelmanipulatie. Hier zijn een aantal functies die u kunt aansluiten op pre / post-save signalen:

import logging
from south.db import db
from django.db import connection

def create_db_table(model_class):
  """ Takes a Django model class and create a database table, if necessary.
  """
  table_name = model_class._meta.db_table
  if (connection.introspection.table_name_converter(table_name)
                    not in connection.introspection.table_names()):
    fields = [(f.name, f) for f in model_class._meta.fields]
    db.create_table(table_name, fields)
    logging.debug("Creating table '%s'" % table_name)

def add_necessary_db_columns(model_class):
  """ Creates new table or relevant columns as necessary based on the model_class.
    No columns or data are renamed or removed.
    XXX: May need tweaking if db_column != field.name
  """
  # Create table if missing
  create_db_table(model_class)

  # Add field columns if missing
  table_name = model_class._meta.db_table
  fields = [(f.column, f) for f in model_class._meta.fields]
  db_column_names = [row[0] for row in connection.introspection.get_table_description(connection.cursor(), table_name)]

  for column_name, field in fields:
    if column_name not in db_column_names:
      logging.debug("Adding field '%s' to table '%s'" % (column_name, table_name))
      db.add_column(table_name, column_name, field)

En daar heb je het! Je kan bellen get_custom_car_model() om een ​​django-model te leveren, dat je kunt gebruiken om normale django-queries uit te voeren:

CarModel = get_custom_car_model(my_definition)
CarModel.objects.all()

Problemen

  • Je modellen zijn verborgen voor Django totdat de code die ze maakt, wordt uitgevoerd. Je kunt echter rennen get_custom_car_model voor elke instantie van uw definities in de class_prepared signaal voor uw definitiemodel.
  • ForeignKeys/ManyToManyFields werkt misschien niet (ik heb het niet geprobeerd)
  • U zult de cache van Django's model willen gebruiken, zodat u geen query's hoeft uit te voeren en het model kunt maken elke keer dat u dit wilt gebruiken. Ik heb dit hierboven voor de eenvoud nagelaten
  • U kunt uw dynamische modellen ophalen in de beheerder, maar u moet ook op dynamische wijze de beheerklasse maken en u met behulp van signalen registreren / opnieuw registreren / afmelden.

Overzicht

Als je tevreden bent met de toegevoegde complicaties en problemen, geniet ervan! Een ervan draait, het werkt precies zoals verwacht dankzij de flexibiliteit van Django en Python. Je kunt je model voeden met Django's ModelForm om de gebruiker zijn instanties te laten bewerken en om query's uit te voeren met behulp van de velden van de database rechtstreeks. Als er iets is dat je hierboven niet begrijpt, dan ben je waarschijnlijk het beste om deze benadering niet te volgen (ik heb bewust niet uitgelegd wat sommige concepten voor beginners zijn). Hou het simpel!

Ik denk echt niet dat veel mensen dit nodig hebben, maar ik heb het zelf gebruikt, waar we veel gegevens in de tabellen hadden en echt, echt nodig om de gebruikers de kolommen te laten aanpassen, die zelden veranderden.


26
2017-09-14 22:52



Database

Overweeg nogmaals uw databaseontwerp.

Je zou moeten denken in termen van hoe de objecten die je wilt vertegenwoordigen in de echte wereld met elkaar in verband staan ​​en vervolgens proberen die relaties zo veel mogelijk te generaliseren (dus in plaats van te zeggen dat elke truck een vergunning heeft, zeg je elk voertuig heeft een attribuut dat ofwel een vergunning, een hoeveelheid lading of wat dan ook kan zijn).

Dus laten we het proberen:

Als u zegt dat u een voertuig hebt en elk voertuig kan veel door de gebruiker gespecificeerde attributen hebben, overweeg dan de volgende modellen:

class Attribute(models.Model):
    type  = models.CharField()
    value = models.CharField()

class Vehicle(models.Model):
    attribute = models.ManyToMany(Attribute)

Zoals eerder opgemerkt, is dit een algemeen idee waarmee u zoveel attributen aan elk voertuig kunt toevoegen als u wilt.

Als u wilt dat een specifieke set attributen beschikbaar is voor de gebruiker die u kunt gebruiken choices in de Attribute.type veld.

ATTRIBUTE_CHOICES = (
    (1, 'Permit'),
    (2, 'Manufacturer'),
)
class Attribute(models.Model):
    type = models.CharField(max_length=1, choices=ATTRIBUTE_CHOICES)
    value = models.CharField()

Misschien wilt u dat elke voertuigsoort zijn eigen set beschikbare attributen heeft. Dit kan gedaan worden door nog een ander model toe te voegen en buitenlandse sleutelrelaties van beide in te stellen Vehicle en Attribute modellen erop.

class VehicleType(models.Model):
    name  = models.CharField()

class Attribute(models.Model):
    vehicle_type = models.ForeigngKey(VehicleType)
    type  = models.CharField()
    value = models.CharField()

class Vehicle(models.Model):
    vehicle_type = models.ForeigngKey(VehicleType)
    attribute = models.ManyToMany(Attribute)

Op deze manier heeft u een duidelijk beeld van hoe elk kenmerk betrekking heeft op een bepaald voertuig.

vormen

Kortom, met dit databaseontwerp hebt u twee formulieren nodig voor het toevoegen van objecten aan de database. Specifiek een model vorm voor een voertuig en een modelformatvoor attributen. U zou jQuery kunnen gebruiken om dynamisch meer items toe te voegen aan de Attribute formset.


Notitie

Je zou ook kunnen scheiden Attribute les naar AttributeType en AttributeValue dus je hebt geen redundante attribuuttypen opgeslagen in je database of als je de attribuutkeuzes voor de gebruiker wilt beperken maar de mogelijkheid wilt behouden om meer typen toe te voegen aan de Django-beheersite.

Om volledig cool te zijn, zou u autocomplete op uw formulier kunnen gebruiken om bestaande attributentypes voor te stellen aan de gebruiker.

wenk: leer meer over database normalisatie.


Andere oplossingen

Zoals voorgesteld in het vorige antwoord door Stuart Marsh

Aan de andere kant kunt u uw modellen coderen voor elk voertuigtype, zodat elk voertuigtype wordt weergegeven door de subklasse van het basisvoertuig en elke subklasse zijn eigen specifieke kenmerken kan hebben, maar dat oplossingen niet erg flexibel zijn (als u flexibiliteit nodig hebt) .

U kunt ook JSON-representatie van extra objectkenmerken in één databaseveld behouden, maar ik weet niet zeker of dit nuttig zou zijn bij het opvragen van kenmerken.


8
2017-09-14 22:52



Hier is mijn eenvoudige test in django shell- ik heb net ingetikt en het lijkt goed te werken-



    In [25]: attributes = {
               "__module__": "lekhoni.models",
               "name": models.CharField(max_length=100),
                "address":  models.CharField(max_length=100),
            }

    In [26]: Person = type('Person', (models.Model,), attributes)

    In [27]: Person
    Out[27]: class 'lekhoni.models.Person'

    In [28]: p1= Person()

    In [29]: p1.name= 'manir'

    In [30]: p1.save()

    In [31]: Person.objects.a
    Person.objects.aggregate  Person.objects.all        Person.objects.annotate   

    In [32]: Person.objects.all()

    Out[33]: [Person: Person object]

Het lijkt heel simpel - niet zeker waarom het niet als een optie beschouwd mag worden - Reflectie is heel gebruikelijk is andere talen zoals C # of Java- Hoe dan ook, ik ben erg nieuw voor django dingen-


2
2017-08-28 04:45



Heb je het over in een front-end interface, of in de Django-beheerder?

Je kunt zo geen echte velden creëren zonder al te veel werk onder de motorkap. Elk model en veld in Django heeft een bijbehorende tabel en kolom in de database. Voor het toevoegen van nieuwe velden is meestal raw sql vereist of migraties met Zuid.

Vanuit een interface aan de voorzijde kunt u pseudo-velden maken en deze in een json-indeling opslaan in een veld met één model.

Maak bijvoorbeeld een tekstveld met andere gegevens in het model. Stel gebruikers vervolgens in staat velden te maken en op te slaan als {'userfield': 'userdata', 'mileage': 54}

Maar ik denk dat als u voertuigen van de eindige klasse gebruikt, u een basismodel met de basiskenmerken van het voertuig maakt en vervolgens modellen maakt die van het basismodel voor elk voertuigtype erft.

class base_vehicle(models.Model):
    color = models.CharField()
    owner_name = models.CharField()
    cost = models.DecimalField()

class car(base_vehicle):
    mileage = models.IntegerField(default=0)

enz


1
2017-09-14 22:03