Изредко работая над своим личным проектом, я дошёл до задачи сделать форму… Форму добавления места. Проблема в том, что она достаточно не типична. Я решил написать по этому поводу статью, которая поможет глубже проникнуть в newforms новичкам.
Всё началось просто, с вот такого кода:
Но тут же появилась мысль, да не одна:
Все эти мысли касаются полей category, metrostation и city. Вопрос валидации значений решился легко. Был создан новый класс поля и в него добавлен метод clean(), выглядящий где-то так:
Плюс ко всем прелестям, в clean_data['category'] мы будим уже иметь готовый для использования объект. Далее последовало огорчение, придётся так для каждого объекта поля создавать. Плюс ко всему, проблему с автоматическим заполнением значениями я так и не решил. Поэтому, было решено создать универсальный класс.
Итак, что это, а главное «нахэто». Я создал потомка обычному полю, в котором определил виджет по-умолчанию как forms.Select (что есть обычный выпадающий список), данные в который подгружаются автоматом при создании поля. При этом, мы не теряем гибкости… Даже прибавляем. Например, лишним является order_by('title') в цикле заполнения (но я это сделал целенаправленно, чтобы каждый раз не добавлять сортировку при создании поля). В чём заключается гибкость. Ещё при определении формы (точнее поля формы) мы задаём круг объектов, по которым хотим чтоб пользователь осуществлял выбор. Точнее передаём переменной object запрос, по которому эти объекты выбирать (LazyLoad). Так же, гибкость заключается в том, что мы без проблем можем изменить widget на сходный с Select (что, впринципе, планируется в будущем).
Далее назрел вопрос с полем тегов. Оно будет использоваться во многих формах, поэтому было решено вынести в отдельный класс, поскольку предполагается его модернизировать (автодополнение, методы отделения тегов и т.п.). Впринципе, вот код:
По сути, просто смотрим есть ли такой тег в БД, если нет — создаём, если есть, выбираем и отдаём списком как очищенный результат. Ещё нужно подумать как проделать обратную операцию на автомате (объекты тегов в строку), но это пусть будет домашним заданием (дабы скрыть некомпитентность автора в данном вопросе :). Так же многие могут заметить класс TagCynonym. Пока ещё думаю как сделать грамотнее сопоставление разных тегов (порно — porno).
Ссылки:
UPD: Вот интересно, за что карму понижают?) Наверное, любители PHP)))
Всё началось просто, с вот такого кода:
from django import newforms as forms<br /> class AddPlaceForm(forms.Form): title = forms.CharField(max_length=50) category = forms.IntegerField() text = forms.CharField(widget=forms.Textarea) tags = forms.CharField(max_length=50)) city = forms.IntegerField() metrostation = forms.IntegerField(null=True) adress = forms.CharField(max_length=255)
Но тут же появилась мысль, да не одна:
- Я чё, млять, совсем дебил проверять все эти числа?
- Я чё, млять, совсем дебил ручками забивать все значения в форму?
Все эти мысли касаются полей category, metrostation и city. Вопрос валидации значений решился легко. Был создан новый класс поля и в него добавлен метод clean(), выглядящий где-то так:
def clean(self, value): from my.site.models import Category try: item = Category.objects.get(pk=value) return item except ObjectDoesNotExist: raise forms.ValidationError(u'Неверный ввод')
Плюс ко всем прелестям, в clean_data['category'] мы будим уже иметь готовый для использования объект. Далее последовало огорчение, придётся так для каждого объекта поля создавать. Плюс ко всему, проблему с автоматическим заполнением значениями я так и не решил. Поэтому, было решено создать универсальный класс.
class SelectFromModel(forms.Field): widget = forms.Select() def __init__(self, objects, *args, **kwargs): self.objects = objects super(SelectFromModel, self).__init__(*args, **kwargs) self.loadChoices() def loadChoices(self): choices = () for object in self.objects.order_by('title'): choices += ((object.id, object.title),) self.widget.choices = choices def clean(self, value): value = int(value) for cat_id, cat_title in self.widget.choices: if cat_id == value: return self.objects.get(pk=cat_id) raise forms.ValidationError(u'Неверный ввод') # Соответственно форма class AddPlaceForm(forms.Form): title = forms.CharField(max_length=50) category = SelectFromModel(objects=Category.objects.all()) text = forms.CharField(widget=forms.Textarea) # Об этом чуть позже tags = TagField() city = SelectFromModel(objects=City.objects.all()) metrostation = SelectFromModel(objects=MetroStation.objects.all()) adress = forms.CharField(max_length=255)
Итак, что это, а главное «нахэто». Я создал потомка обычному полю, в котором определил виджет по-умолчанию как forms.Select (что есть обычный выпадающий список), данные в который подгружаются автоматом при создании поля. При этом, мы не теряем гибкости… Даже прибавляем. Например, лишним является order_by('title') в цикле заполнения (но я это сделал целенаправленно, чтобы каждый раз не добавлять сортировку при создании поля). В чём заключается гибкость. Ещё при определении формы (точнее поля формы) мы задаём круг объектов, по которым хотим чтоб пользователь осуществлял выбор. Точнее передаём переменной object запрос, по которому эти объекты выбирать (LazyLoad). Так же, гибкость заключается в том, что мы без проблем можем изменить widget на сходный с Select (что, впринципе, планируется в будущем).
Далее назрел вопрос с полем тегов. Оно будет использоваться во многих формах, поэтому было решено вынести в отдельный класс, поскольку предполагается его модернизировать (автодополнение, методы отделения тегов и т.п.). Впринципе, вот код:
class TagField(forms.CharField): def clean(self, value): from daparty.site.models import Tag, TagCynonym tags = value.split(',') resoult = [] for tag in tags: tag = tag.strip().lower() # Ищем в существующих тегах либо создаём dbtag = Tag.objects.get_or_create(title=tag) resoult.append(dbtag) return resoult
По сути, просто смотрим есть ли такой тег в БД, если нет — создаём, если есть, выбираем и отдаём списком как очищенный результат. Ещё нужно подумать как проделать обратную операцию на автомате (объекты тегов в строку), но это пусть будет домашним заданием (дабы скрыть некомпитентность автора в данном вопросе :). Так же многие могут заметить класс TagCynonym. Пока ещё думаю как сделать грамотнее сопоставление разных тегов (порно — porno).
Ссылки:
UPD: Вот интересно, за что карму понижают?) Наверное, любители PHP)))