📜 ⬆️ ⬇️

Own form field in Django


Hi, Habr!
I am a very big fan of the Django framework and I write all my projects exclusively on it. Today I will talk about how to extend the standard library of form fields with your own original solution. The task of the article is not to offer a ready-made solution, but to highlight the technology for creating custom fields.
A small digression. Once I pored over the creation of a knowledge base for a company in which I was working at that time. The base was a collection of articles tagged. The following requirements were placed on the tag entry:

After a brief search, I found the Tag-It jQuery plugin ! which fully satisfied the requirements for the widget. It remains only to fasten this field to Django.


Data source for autocompletion


Our widget will take data from the model, where, in fact, the entered tags are saved
from django.db import models from unicodedata import category from django.utils.http import urlquote import re class Tag(models.Model): """ Model of Tags """ name = models.CharField(max_length=200, null=False, verbose_name="Tag name") slug = models.CharField(max_length=400, editable=False, verbose_name=u'Slug', unique=True, null=False) def __unicode__(self): return self.name @staticmethod def _generate_slug(value): slug = ''.join(ch for ch in value[:200] if category(ch)[0] != 'P') return urlquote(re.sub(r'([ ]+_)|(_[ ]+)|([ ]+)', '_', slug)) def save(self, *args, **kwargs): self.name = self.name.lower() self.slug = self._generate_slug(self.name) super(Tag, self).save(*args, **kwargs) @classmethod def get_or_create(cls, value): slug = cls._generate_slug(value.lower().strip()) if cls.objects.filter(slug=slug).exists(): return cls.objects.get(slug=slug) else: return cls.objects.create(name=value.lower().strip()) 

Since tags can contain spaces, and other garbage, we introduce into the model a slug field that clearly identifies the tag by its content, no matter how many spaces between the words in the tag name. We also introduce the class method get_or_create, which returns a tag if it is found by the slug field, or creates a new tag in the opposite case. In addition, before creating a new tag, we reduce it to lowercase in the save method for consistency.

View for autocompletion


We sketch a small view that returns a list of tags beginning with the characters entered.
Tag-It plugin! passes the entered string to the term variable.
 from models import Tag from django.http import HttpResponse import json def tag_autocomplete(request): """ url: /tag_autocomplete/""" value = request.GET['term'] available_tags = Tag.objects.filter(name__startswith=value.lower()) response = HttpResponse(json.dumps([unicode(tag) for tag in available_tags]), content_type="application/json") return response 

')

Widget and form field


Widget and form field can be declared directly at the place of their application - in forms.py. I did, because I did not plan to use it anywhere else.
I inherited the widget from the hidden input field, because the visualization is handled by the Tag-It plugin! ..
 from django import forms class TagitWidget(forms.HiddenInput): """ Widget on the basis of Tag-It! http://aehlke.github.com/tag-it/""" class Media: js = (settings.STATIC_URL + 'js/tag-it.js', settings.STATIC_URL + 'js/tagit_widget.js',) css = {"all": (settings.STATIC_URL + 'css/jquery.tagit.css',)} 

tag-it.js and jquery.tagit.css are tag-it! plug-in files. .. The contents of tagit_widget.js will be described below.

 class TagitField(forms.Field): """ Tag field """ widget = TagitWidget def __init__(self, tag_model, *args, **kwargs): self.tag_model = tag_model super(TagitField, self).__init__(*args, **kwargs) def to_python(self, value): tag_strings = value.split(',') return [self.tag_model.get_or_create(tag_string) for tag_string in tag_strings if len(tag_string) > 0] def validate(self, value): if len(value) == 0 and self.required: raise ValidationError(self.error_messages['required']) def prepare_value(self, value): if value is not None and hasattr(value, '__iter__'): return ','.join((unicode(tag) for tag in value)) return value def widget_attrs(self, widget): res = super(TagitField, self).widget_attrs(widget) or {} res["class"] = "tagit" return res 

Specify the widget in the form field declaration. In the constructor, besides the usual parameters, we pass the tag model, with which we convert the list of tag names into the list of tag objects in the to_python method. The prepare_value method does the inverse transform. In the widget_attrs method, we add the “class” attribute to the hidden field, by which the script will find the required fields for applying the Tag-It! Plugin to them.
The script itself is in the tagit_widget.js file and looks like this:
 $(document).ready(function() { $(".tagit").tagit({ allowSpaces: true, autocomplete: {delay: 0, minLength: 2, source: "/tag_autocomplete/" } }); }); 

About additional options of the plugin can be found here . I can only say that here I allow tags to contain spaces (allowSpaces), do autocompletion without delay after input (delay), starting from the second entered character (minLength) and taking options from our view (source).

Conclusion


The field is ready to use. You can apply it as follows:
 from models import Tag class SomeForm(forms.Form): tag = TagitField(Tag, label='Tags', required=True) 

The main thing is not to forget to connect statics from this form in the template.
 <!doctype html> <html> <head> <title>Tag-It!</title> {{some_form.media}} </head> <body> <form action=""> {{some_form.as_p}} </form> </body> </html> 

Enjoy django coding.

Source: https://habr.com/ru/post/176807/


All Articles