Implementando jquery tag inputs en Django

Parecía sencillo, pero finalmente costó lo suyo dar con una solución que le gustase a Django y a jQuery Tag inputs, un excelente plugin para organizar tags dentro de un textarea que cumplía con todas las necesidades que Sabrosous necesita.

la parte más compleja por el momento ha sido al formatear las etiquetas/tags de un enlace que ya estaban en la Base de datos, ya que por defecto Django muestra sólo las claves primarias en los ManyToManyFields al hacer el render del formulario. Lo hace llamando a la función model_to_dict en django.forms.


if isinstance(f, ManyToManyField):
            # If the object doesn't have a primary key yet, just use an empty
            # list for its m2m fields. Calling f.value_from_object will raise
            # an exception.
            if instance.pk is None:
                data[f.name] = []
            else:
                # MultipleChoiceWidget needs a list of pks, not object instances.
                data[f.name] = [obj.pk for obj in f.value_from_object(instance)]

Al intentar reescribir la función model_to_dict en mi archivo links/forms.py me di cuenta que no tomaba esta sobreescritura, así que tuve que reescribir el constructor de mi clase LinkForm, que hereda de ModelForm. Más adelante pensaré en una mejor solución, aunque ésta por el momento me parece bastante decente.

Así que finalmente modificando una línea en el model_to_dict en el módulo forms conseguimos que al mostrar los tags seleccionados no muestre el id sino el campo que nosotros queramos. Al hacer esto faltará procesar este campo (tags) del formulario para guardarlo correctamente en la base de datos al hacer submit, así que recomiendo mostrar un campo único, en mi caso, un slug.

Aquí cómo queda el archivo final, las partes importantes son LinkForm y model_to_dict.


from django import forms
from django.forms import ModelForm
from links.models import Link, Tag

from django.forms.util import ErrorList


def model_to_dict(instance, fields=None, exclude=None):
    """
    Returns a dict containing the data in ``instance`` suitable for passing as
    a Form's ``initial`` keyword argument.

    ``fields`` is an optional list of field names. If provided, only the named
    fields will be included in the returned dict.

    ``exclude`` is an optional list of field names. If provided, the named
    fields will be excluded from the returned dict, even if they are listed in
    the ``fields`` argument.
    """
    # avoid a circular import
    from django.db.models.fields.related import ManyToManyField
    opts = instance._meta
    data = {}
    for f in opts.fields + opts.many_to_many:
        if not f.editable:
            continue
        if fields and not f.name in fields:
            continue
        if exclude and f.name in exclude:
            continue
        if isinstance(f, ManyToManyField):
            # If the object doesn't have a primry key yet, just use an empty
            # list for its m2m fields. Calling f.value_from_object will raise
            # an exception.
            if instance.pk is None:
                data[f.name] = []
            else:
                #mi linea para TAG INPUTS
                data[f.name] = ", ".join([obj.slug for obj in f.value_from_object(instance)])
        else:
            data[f.name] = f.value_from_object(instance)
    return data


class LinkForm(ModelForm):
    tags = forms.ModelMultipleChoiceField(
                                queryset=Tag.objects.all(),
                                widget=forms.Textarea
                              )

    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
                 initial=None, error_class=ErrorList, label_suffix=':',
                 empty_permitted=False, instance=None):
        opts = self._meta
        if instance is None:
            if opts.model is None:
                raise ValueError('ModelForm has no model class specified.')
            # if we didn't get an instance, instantiate a new one
            self.instance = opts.model()
            object_data = {}
        else:
            self.instance = instance
            object_data = model_to_dict(instance, opts.fields, opts.exclude)
        # if initial was provided, it should override the values from instance
        if initial is not None:
            object_data.update(initial)
        # self._validate_unique will be set to True by BaseModelForm.clean().
        # It is False by default so overriding self.clean() and failing to call
        # super will stop validate_unique from being called.
        self._validate_unique = False
        super(ModelForm, self).__init__(data, files, auto_id, prefix, object_data,
                                        error_class, label_suffix, empty_permitted)

    class Meta:
        model = Link
        fields = ('title', 'url', 'description', 'tags')


class UploadFileForm(forms.Form):
    file = forms.FileField()

Javier Aguirre

Read more posts by this author.

comments powered by Disqus