8
votes

Comment choisiriez-vous de et remplaceriez-vous les classes de modèle Django pour créer une ListOfstringfield?

Je souhaite créer un nouveau type de champ pour les modèles Django qui est fondamentalement une liste de lecture. Donc, dans votre code modèle, vous auriez les suivants:

modèles.py: xxx

autre.py: xxx

Comment allez-vous écrire ce type de champ, avec les stipulations suivantes?

  • Nous ne voulons pas faire créer un champ qui jette des jetons ensemble et les sépare avec un jeton dans un champ comme Ce . C'est une bonne solution dans certains cas, mais nous voulons conserver les données de chaîne normalisées de sorte que les outils autres que Django peuvent interroger les données.
  • Le champ doit créer automatiquement toutes les tables secondaires nécessaires pour stocker les données de chaîne.
  • La table secondaire doit idéalement avoir une seule copie de chaque chaîne unique. Ceci est facultatif, mais serait bien d'avoir.

    regarder dans le code Django, on dirait que je voudrais faire quelque chose de similaire à ce que fait la tâche de l'entreprise étrangère, mais la documentation est clairsemée.

    Ceci conduit aux questions suivantes:

    • peut-il être fait?
    • a-t-il été fait (et si oui, où)?
    • Y a-t-il une documentation sur Django sur la manière de prolonger et de remplacer leurs classes de modèle, en particulier de leurs classes de relations? Je n'ai pas vu beaucoup de documentation sur cet aspect de leur code, mais il y a Ceci .

      Ceci est proviennent de cette question .


4 commentaires

C'est certainement possible, bien que je ne connais pas les détails. Je dis à lire leur liste de diffusion et à regarder à travers les documents, mais vous devrez finalement avoir à creuser à travers la source de Django.


Je suis en train de creuser à travers la source maintenant. Si je parviens à proposer une chose qui fonctionne, je vais essayer de le poster ici.


Posté ma tentative ci-dessous, qui semble fonctionner.


Donc, ma tentative ci-dessous fonctionne, mais est extrêmement fragile et sujette d'erreur. :(


5 Réponses :


-1
votes

Je pense que ce que vous voulez est un champ modèle personnalisé .


0 commentaires


5
votes

Je pense aussi que vous allez à ce sujet le mauvais sens. Essayer de faire un champ Django Créer une table de base de données auxiliaire est presque certainement la mauvaise approche. Il serait très difficile de faire et confondrait probablement des développeurs tiers si vous essayez de rendre votre solution généralement utile.

Si vous essayez de stocker une blob de données dénormalisée dans une seule colonne, je prendrais Une approche similaire à celle que vous avez liée, sérialisant la structure de données Python et la stockant dans un champ de texte. Si vous souhaitez que les outils autres que Django puissent fonctionner sur les données, vous pouvez Serialize à JSON (ou un autre format qui dispose d'un support de langue large): P>

from django.db import models
from django.utils import simplejson

class JSONDataField(models.TextField):
    __metaclass__ = models.SubfieldBase

    def to_python(self, value):
        if value is None: 
            return None
        if not isinstance(value, basestring): 
            return value
        return simplejson.loads(value)

    def get_db_prep_save(self, value):
        if value is None: 
            return None
        return simplejson.dumps(value)


3 commentaires

Je viens de recevoir une chance de regarder le lien que vous avez posté. Je pense qu'il est plus proche de ce que j'essaie de faire de la mise en œuvre de la mise en œuvre que ce que j'ai actuellement. Merci!


+1 Pour seulement utiliser cette approche si sa dénormalisation! Sinon, normaliser!


Notez que get_db_prep_save prend maintenant 4 arguments (à partir de Django 1.5)



2
votes

Merci pour tous ceux qui ont répondu. Même si je n'ai pas utilisé votre réponse directement, les exemples et les liens me gênais dans la bonne direction.

Je ne suis pas sûr de la production, mais cela semble fonctionner dans tous mes tests jusqu'à présent. P>

class ListValueDescriptor(object):

   def __init__(self, lvd_parent, lvd_model_name, lvd_value_type, lvd_unique, **kwargs):
      """
         This descriptor object acts like a django field, but it will accept
         a list of values, instead a single value.
         For example:
            # define our model
            class Person(models.Model):
               name = models.CharField(max_length=120)
               friends = ListValueDescriptor("Person", "Friend", "CharField", True, max_length=120)

            # Later in the code we can do this
            p = Person("John")
            p.save() # we have to have an id
            p.friends = ["Jerry", "Jimmy", "Jamail"]
            ...
            p = Person.objects.get(name="John")
            friends = p.friends
            # and now friends is a list.
         lvd_parent - The name of our parent class
         lvd_model_name - The name of our new model
         lvd_value_type - The value type of the value in our new model
                        This has to be the name of one of the valid django
                        model field types such as 'CharField', 'FloatField',
                        or a valid custom field name.
         lvd_unique - Set this to true if you want the values in the list to
                     be unique in the table they are stored in. For
                     example if you are storing a list of strings and
                     the strings are always "foo", "bar", and "baz", your
                     data table would only have those three strings listed in
                     it in the database.
         kwargs - These are passed to the value field.
      """
      self.related_set_name = lvd_model_name.lower() + "_set"
      self.model_name = lvd_model_name
      self.parent = lvd_parent
      self.unique = lvd_unique

      # only set this to true if they have not already set it.
      # this helps speed up the searchs when unique is true.
      kwargs['db_index'] = kwargs.get('db_index', True)

      filter = ["lvd_parent", "lvd_model_name", "lvd_value_type", "lvd_unique"]

      evalStr = """class %s (models.Model):\n""" % (self.model_name)
      evalStr += """    value = models.%s(""" % (lvd_value_type)
      evalStr += self._params_from_kwargs(filter, **kwargs) 
      evalStr += ")\n"
      if self.unique:
         evalStr += """    parent = models.ManyToManyField('%s')\n""" % (self.parent)
      else:
         evalStr += """    parent = models.ForeignKey('%s')\n""" % (self.parent)
      evalStr += "\n"
      evalStr += """self.innerClass = %s\n""" % (self.model_name)

      print evalStr

      exec (evalStr) # build the inner class

   def __get__(self, instance, owner):
      value_set = instance.__getattribute__(self.related_set_name)
      l = []
      for x in value_set.all():
         l.append(x.value)

      return l

   def __set__(self, instance, values):
      value_set = instance.__getattribute__(self.related_set_name)
      for x in values:
         value_set.add(self._get_or_create_value(x))

   def __delete__(self, instance):
      pass # I should probably try and do something here.


   def _get_or_create_value(self, x):
      if self.unique:
         # Try and find an existing value
         try:
            return self.innerClass.objects.get(value=x)
         except django.core.exceptions.ObjectDoesNotExist:
            pass

      v = self.innerClass(value=x)
      v.save() # we have to save to create the id.
      return v

   def _params_from_kwargs(self, filter, **kwargs):
      """Given a dictionary of arguments, build a string which 
      represents it as a parameter list, and filter out any
      keywords in filter."""
      params = ""
      for key in kwargs:
         if key not in filter:
            value = kwargs[key]
            params += "%s=%s, " % (key, value.__repr__())

      return params[:-2] # chop off the last ', '

class Person(models.Model):
   name = models.CharField(max_length=120)
   friends = ListValueDescriptor("Person", "Friend", "CharField", True, max_length=120)


2 commentaires

J'ai remarqué avec cette classe, que vous devez enregistrer avant d'ajouter, puisque l'identifiant n'existe pas tant que vous économisez. Je pense que cela pourrait être réparé / corrigé si cela a hérité du champ associé et du champ, mais j'essaie toujours d'envelopper ma tête autour de ce code.


Après plus d'expérimentations, cela s'avère travailler, mais est extrêmement fragile, surtout en ce qui concerne les espaces de noms, il doit vivre dans des modèles.py. Je continuerai à travailler dessus et j'espère développer une version plus propre.



5
votes

Qu'est-ce que vous avez décrit me semble vraiment similaire aux tags.
Alors, pourquoi ne pas utiliser
django marquant ?
Cela fonctionne comme un charme, vous pouvez l'installer indépendamment de votre application et son API est assez facile à utiliser.


2 commentaires

De plus, si cela ne fonctionne pas comme le veut, il est facile de regarder comment cela est fait là-bas et la modifier à ses besoins.


Joli! Je vais devoir examiner cela plus loin.