Skip to content

Instantly share code, notes, and snippets.

@cb109
Last active September 25, 2024 08:02

Revisions

  1. cb109 revised this gist Feb 12, 2021. No changes.
  2. cb109 revised this gist Feb 12, 2021. 2 changed files with 24 additions and 5 deletions.
    4 changes: 2 additions & 2 deletions template.html
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    <!-- To make the AutocompleteSelect work we need to include some CSS and javascript -->
    {{ select_workspace_form.media }}
    {{ autocomplete_form.media }}

    <form id="mycustommodel_form">
    {{ select_workspace_form }}
    {{ autocomplete_form }}
    <button type="submit">Submit</button>
    </form>
    25 changes: 22 additions & 3 deletions views.py
    Original file line number Diff line number Diff line change
    @@ -1,19 +1,38 @@
    from typing import Callable
    from typing import Iterable

    from django.http import JsonResponse

    from myproject.myapp.forms import MyCustomModelForm
    from myproject.myapp.models import MyCustomModel
    from myproject.myapp.utils import search


    def make_autocomplete_response(
    queryset: Iterable, instance_to_text_func: Callable
    ) -> JsonResponse:
    return JsonResponse(
    {
    "results": [
    {"id": instance.id, "text": instance_to_text_func(instance)}
    for instance in queryset
    ],
    "pagination": {"more": False},
    }
    )


    def search_mycustommodel(request):
    term = request.GET.get("term", "")
    candidates = search(Workspace.objects, term, ["name"]).order_by(
    candidates = search(MyCustomModel.objects, term, ["name"]).order_by(
    "name",
    )
    return make_autocomplete_response(
    workspace_candidates, lambda workspace: workspace.name
    candidates, lambda instance: instance.name
    )


    def template(request, pk):
    def template(request, pk: int):
    instance = MyCustomModel.objects.get(pk=pk)
    autocomplete_form = MyCustomModelForm(
    initial={"mycustommodel": instance},
  3. cb109 revised this gist Feb 12, 2021. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion views.py
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,4 @@
    from myproject.myapp.forms import MyCustomModelForm
    from myproject.myapp.models import MyCustomModel
    from myproject.myapp.utils import search

    @@ -14,7 +15,7 @@ def search_mycustommodel(request):

    def template(request, pk):
    instance = MyCustomModel.objects.get(pk=pk)
    autocomplete_form = SelectWorkspaceForm(
    autocomplete_form = MyCustomModelForm(
    initial={"mycustommodel": instance},
    )
    return render_to_response(
  4. cb109 created this gist Feb 12, 2021.
    74 changes: 74 additions & 0 deletions forms.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,74 @@
    from typing import Iterable
    from typing import Optional

    from django.contrib import admin
    from django.contrib.admin.widgets import AutocompleteSelect
    from django.forms import ModelChoiceField

    from myproject.myapp.models import MyCustomModel


    class CustomUrlAutocompleteSelect(AutocompleteSelect):
    """URL is not based on url_name implicitly, instead passed in as a kwarg."""

    def __init__(self, *args, **kwargs):
    self.url = kwargs.pop("url")
    super().__init__(*args, **kwargs)

    def get_url(self):
    return self.url


    def build_modelchoicefield(
    autocomplete_url: str,
    model: type,
    fieldname: str,
    queryset: Iterable,
    label: Optional[str] = None,
    required: bool = False,
    ) -> ModelChoiceField:
    # FIXME:
    # Our shenanigans to use the AutocompleteSelect outside of the
    # admin have a little drawback here: The queryset that we pass
    # will be used to check for a valid selection, but it is not
    # what the API returns, so it will not affect what we see in
    # the dropdown, unfortunately. It will however be respected
    # when we submit the form and display an error 'invalid
    # selection'.
    options = {
    "queryset": queryset,
    "widget": CustomUrlAutocompleteSelect(
    model._meta.get_field(fieldname).remote_field,
    admin.site,
    url=autocomplete_url,
    ),
    "required": required,
    }
    if label:
    options["label"] = label
    return ModelChoiceField(**options)



    class MyCustomModelForm(forms.ModelForm):
    """Note: Make sure to include {{ form.media }} in the template."""

    class Meta:
    model = MyCustomModel
    fields = ("id",)

    def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)

    self.fields["id"] = build_modelchoicefield(
    autocomplete_url="/autocomplete/mycustommodel",
    model=MyCustomModel,
    fieldname="id",
    queryset=MyCustomModel.objects.all(),
    required=False,
    )

    # HACK: Rename field to use a less generic input name in html,
    # otherwise the selet would have name="id".
    self.fields["mycustommodel"] = self.fields["id"]
    del self.fields["id"]
    7 changes: 7 additions & 0 deletions template.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,7 @@
    <!-- To make the AutocompleteSelect work we need to include some CSS and javascript -->
    {{ select_workspace_form.media }}

    <form id="mycustommodel_form">
    {{ select_workspace_form }}
    <button type="submit">Submit</button>
    </form>
    10 changes: 10 additions & 0 deletions urls.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,10 @@
    ...

    urlpatterns = [
    ...
    url(
    r"^autocomplete/mycustommodel$",
    myproject.myapp.views.search_mycustommodel,
    ),
    ...
    ]
    23 changes: 23 additions & 0 deletions views.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,23 @@
    from myproject.myapp.models import MyCustomModel
    from myproject.myapp.utils import search


    def search_mycustommodel(request):
    term = request.GET.get("term", "")
    candidates = search(Workspace.objects, term, ["name"]).order_by(
    "name",
    )
    return make_autocomplete_response(
    workspace_candidates, lambda workspace: workspace.name
    )


    def template(request, pk):
    instance = MyCustomModel.objects.get(pk=pk)
    autocomplete_form = SelectWorkspaceForm(
    initial={"mycustommodel": instance},
    )
    return render_to_response(
    "myapp/template.html", {"autocomplete_form": autocomplete_form}
    )