# List and Create Articles ## Example Overview This is one of the most comprehensive examples showcasing the capabilities of `django_tomselect` in a real-world scenario. The **List and Create Articles** example demonstrates how to integrate `django_tomselect` for filtering and managing articles efficiently. The "List" view enables filtering based on various attributes like edition year and word count using `django_tomselect` with iterables, while the "Create / Update" views facilitate adding or updating articles with fields powered by `django_tomselect`. We also incorporate the filter-by and exclude-by examples into the form to demonstrate the flexibility of the plugin. **Objective**: - Showcase dynamic filtering in the article list view using `TomSelectChoiceField` and `TomSelectModelChoiceField`. - Demonstrate a feature-rich forms leveraging the power of plugins and configurations in `django_tomselect`. **Visual Examples**    --- ## Key Code Segments ### Forms #### List Articles Filters The filtering form utilizes `TomSelectChoiceField` for selecting year and word count ranges. :::{admonition} Form Definition :class: dropdown ```python class EditionYearForm(forms.Form): """Form for selecting an edition year.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields["year"].help_text = "This field is backed by the edition_year list in models.py" year = TomSelectChoiceField( config=TomSelectConfig( url="autocomplete-edition-year", value_field="value", label_field="label", placeholder=_("Select an edition year..."), preload="focus", highlight=True, minimum_query_length=0, ), ) class WordCountForm(forms.Form): """Form for selecting a word count range.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields["word_count"].help_text = "This field is backed by the word_count_range tuple in models.py" word_count = TomSelectChoiceField( config=TomSelectConfig( url="autocomplete-page-count", value_field="value", label_field="label", placeholder=_("Select a word count range..."), preload="focus", highlight=True, minimum_query_length=0, plugin_remove_button=PluginRemoveButton(), ), ) ``` ::: #### Create Article Form The "Create Article" form employs `TomSelectModelChoiceField` and `TomSelectModelMultipleChoiceField` to manage relationships like authors, categories, and more. :::{admonition} Form Definition :class: dropdown ```python category_header = PluginDropdownHeader( title=_("Category Selection"), show_value_field=False, extra_columns={ "parent_name": _("Parent"), "direct_articles": _("Direct Articles"), "total_articles": _("Total Articles"), }, ) author_header = PluginDropdownHeader( title=_("Author Selection"), show_value_field=False, extra_columns={ "article_count": _("Articles"), }, ) class DynamicArticleForm(forms.ModelForm): """Form for creating and editing articles with dynamic fields and dependencies.""" title = forms.CharField(max_length=200, widget=forms.TextInput(attrs={"class": "form-control"})) word_count = forms.IntegerField(widget=forms.NumberInput(attrs={"class": "form-control"}), min_value=0) status = TomSelectChoiceField( config=TomSelectConfig( url="autocomplete-article-status", value_field="value", label_field="label", placeholder=_("Select an article status..."), preload="focus", highlight=True, minimum_query_length=0, ), ) priority = TomSelectChoiceField( config=TomSelectConfig( url="autocomplete-article-priority", value_field="value", label_field="label", placeholder=_("Select an article priority..."), preload="focus", highlight=True, minimum_query_length=0, ), ) magazine = TomSelectModelChoiceField( config=TomSelectConfig( url="autocomplete-magazine", show_list=True, show_create=True, show_update=True, show_delete=True, value_field="id", label_field="name", placeholder=_("Select a magazine..."), preload="focus", highlight=True, minimum_query_length=0, plugin_dropdown_footer=PluginDropdownFooter(), ), ) primary_author = TomSelectModelChoiceField( config=TomSelectConfig( url="autocomplete-author", show_list=True, show_create=True, show_update=True, value_field="id", label_field="name", placeholder=_("Select primary author..."), highlight=True, plugin_dropdown_header=author_header, plugin_dropdown_footer=PluginDropdownFooter(), plugin_dropdown_input=PluginDropdownInput(), plugin_clear_button=PluginClearButton(title=_("Clear primary author")), ), ) contributing_authors = TomSelectModelMultipleChoiceField( config=TomSelectConfig( url="autocomplete-author", value_field="id", label_field="name", exclude_by=("primary_author", "id"), # Exclude primary author placeholder=_("Select contributing authors..."), highlight=True, max_items=None, plugin_dropdown_header=author_header, plugin_dropdown_input=PluginDropdownInput(), plugin_clear_button=PluginClearButton(title=_("Clear all contributing authors")), plugin_remove_button=PluginRemoveButton(), ), ) main_category = TomSelectModelChoiceField( config=TomSelectConfig( url="autocomplete-category", show_list=True, show_create=True, show_update=True, value_field="id", label_field="name", placeholder=_("Select main category..."), highlight=True, plugin_dropdown_header=category_header, plugin_dropdown_footer=PluginDropdownFooter(), plugin_dropdown_input=PluginDropdownInput(), plugin_clear_button=PluginClearButton(title=_("Clear main category")), ), ) subcategories = TomSelectModelMultipleChoiceField( config=TomSelectConfig( url="autocomplete-category", value_field="id", label_field="full_path", show_update=True, show_delete=True, filter_by=("main_category", "parent_id"), placeholder=_("Select subcategories..."), highlight=True, max_items=None, plugin_dropdown_header=category_header, plugin_dropdown_input=PluginDropdownInput(), plugin_clear_button=PluginClearButton(title=_("Clear all subcategories")), plugin_remove_button=PluginRemoveButton(), ), ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Set help text for fields self.fields["status"].help_text = "This field is backed by ArticleStatus (models.TextChoices) in models.py" self.fields["priority"].help_text = ( "This field is backed by ArticlePriority (models.IntegerChoices) in models.py" ) self.fields["magazine"].help_text = "This field is backed by the Magazine model" if "edition" in self.fields.keys(): self.fields["edition"].help_text = ( "This field is backed by the Edition model, and is filtered by the currently selected magazine" ) self.fields["primary_author"].help_text = "This field is backed by the Author model" self.fields["contributing_authors"].help_text = ( "This field is backed by the Author model, and excludes the current selection in primary_author" ) self.fields["main_category"].help_text = "This field is backed by the Category model" self.fields["subcategories"].help_text = ( "This field is backed by the Category model, and is filtered by the main_category, but it only allows " "selections when the main_category is a 'parent' category (e.g.: has no parent itself)" ) # Only try to set initial values if we have an existing instance with an id if self.instance and self.instance.pk: # Set initial values for categories if instance exists if self.instance.categories.exists(): categories = self.instance.categories.all() # Find main category (parent is None) and subcategories main_category = next((cat for cat in categories if cat.parent is None), None) subcategories = [cat for cat in categories if cat.parent is not None] if main_category: self.fields["main_category"].initial = main_category.pk if subcategories: self.fields["subcategories"].initial = [cat.pk for cat in subcategories] # Set initial values for authors if they exist if self.instance.authors.exists(): authors = self.instance.authors.all() self.fields["primary_author"].initial = authors[0].pk if authors else None if len(authors) > 1: self.fields["contributing_authors"].initial = [author.pk for author in authors[1:]] # Dynamically add edition field if magazine exists if self.instance.magazine: self.fields["edition"] = TomSelectModelChoiceField( config=TomSelectConfig( url="autocomplete-edition", show_list=True, show_create=True, show_update=True, show_delete=True, value_field="id", label_field="name", filter_by=("magazine", "magazine_id"), placeholder=_("Select an edition..."), highlight=True, plugin_dropdown_footer=PluginDropdownFooter(), ), initial=( self.instance.edition.pk if hasattr(self.instance, "edition") and hasattr(self.instance.edition, "pk") else None ), ) def clean(self): """Validate the form data.""" cleaned_data = super().clean() # Validate author selections primary_author = cleaned_data.get("primary_author") contributing_authors = cleaned_data.get("contributing_authors", []) if primary_author and primary_author in contributing_authors: self.add_error( "contributing_authors", _("Primary author cannot also be a contributing author"), ) # Validate category hierarchy main_category = cleaned_data.get("main_category") subcategories = cleaned_data.get("subcategories", []) if main_category and subcategories: invalid_subcats = [cat for cat in subcategories if cat.parent_id != main_category.id] if invalid_subcats: self.add_error( "subcategories", _("Selected subcategories must belong to the main category"), ) return cleaned_data def save(self, commit=True): """Save the form, handling the M2M relationships properly.""" article = super().save(commit=False) if commit: article.save() # Handle authors authors = [] if self.cleaned_data.get("primary_author"): authors.append(self.cleaned_data["primary_author"]) if self.cleaned_data.get("contributing_authors"): authors.extend(self.cleaned_data["contributing_authors"]) # Set the authors article.authors.set(authors) # Handle categories categories = [] if self.cleaned_data.get("main_category"): categories.append(self.cleaned_data["main_category"]) if self.cleaned_data.get("subcategories"): categories.extend(self.cleaned_data["subcategories"]) # Set the categories article.categories.set(categories) # Handle edition if it exists in cleaned_data if "edition" in self.cleaned_data and self.cleaned_data["edition"]: # Need to update this through a direct database update # since edition is not a direct field on the Article model Article.objects.filter(pk=article.pk).update(edition=self.cleaned_data["edition"]) return article class Meta: """Meta options for the model form.""" model = Article fields = [ "title", "status", "priority", "magazine", "primary_author", "contributing_authors", "main_category", "subcategories", "word_count", ] ``` ::: --- ### Templates #### List Articles The filter form and article list are rendered in the template with built-in styling and dynamic updates. :::{admonition} Template :class: dropdown ```html {% extends "example/base_with_bootstrap5.html" %} {% load static %} {% block extra_header %} {{ edition_year_form.media }} {% endblock %} {% block content %}
| Title | Magazine | Authors | Categories | Priority | Status | Actions |
|---|---|---|---|---|---|---|
|
{{ article.title }}
Word Count: {{ article.word_count }}
|
{{ article.magazine.name }}
Edition: {{ article.edition.name }}
|
{% for author in article.authors.all %}
{{ author.name }}
{% if forloop.first %}
Primary
{% endif %}
{% endfor %}
|
{% for category in article.categories.all %}
{% if category.parent %}
{{ category.parent.name }} >>
{% endif %}
{{ category.name }}
{% endfor %}
|
{% if article.priority == 1 or article.priority == 7 or article.priority == 13 or article.priority == 17 or article.priority == 20 or article.priority == 21 or article.priority == 22 or article.priority == 29 or article.priority == 32 %} {{ article.get_priority_display }} {% elif article.priority == 2 or article.priority == 18 or article.priority == 30 %} {{ article.get_priority_display }} {% elif article.priority == 3 or article.priority == 4 or article.priority == 5 or article.priority == 6 or article.priority == 8 or article.priority == 9 or article.priority == 10 or article.priority == 11 or article.priority == 12 or article.priority == 14 or article.priority == 15 or article.priority == 16 or article.priority == 19 or article.priority == 23 or article.priority == 24 or article.priority == 25 or article.priority == 26 or article.priority == 27 or article.priority == 28 %} {{ article.get_priority_display }} {% else %} {{ article.get_priority_display }} {% endif %} | {% if article.status == 'draft' %} Draft {% elif article.status == 'active' %} Active {% elif article.status == 'archived' %} Archived {% else %} {{ article.get_status_display }} {% endif %} |