Home > front end >  Create object and display it in same page with htmx
Create object and display it in same page with htmx

Time:05-22

The function I am trying to implement is that, when the form is submitted in the create view, I would like to display the created object beneath it without the page refreshing. I know the way to implement this is through HTMX. The object that is being created is a key which is unique and gets randomly generated. However it does not work properly. Right now, first time clicking the button it creates a key and displays it but the second time it tries to show the previous key and because it already exists it is not unique and therefore it shows nothing.

This is the model

class Key(models.Model):
    key = models.CharField(max_length=10, null=False, default=get_random_string(length=10), unique=True)
    amount = models.IntegerField(default=1)
    created = models.DateTimeField(auto_now_add=True)

    def get_absolute_url(self):
        return reverse("key", kwargs={"id": self.id })

I have two views, a create view and a detail view, I would have liked to used CBV but I can't get my head around how I would implement that, so I am sticking to function based views. Right now the views are looking like this

@login_required
def key_create_view(request):
    form = CreateKeyForm(request.POST or None)

    context = {
        "form": form,
    }
    if form.is_valid():
        obj = form.save(commit=False)
        obj.save()
        key_url = reverse('key', kwargs={'id':obj.id})
        if request.htmx:
            context = {
                 "object": obj,
                 "key_url": key_url
             }
            return render(request, "base/components/key.html", context)
        #return redirect(obj.get_absolute_url())
    return render(request, "base/form.html", context)  



@login_required
def key_detail_hx_view(request, id=None):
    if not request.htmx:
        raise Http404
    try:
        obj = Key.objects.get(id=id)
    except:
        obj = None
    if obj is  None:
        return HttpResponse("Something went wrong")
    context = {
        "object": obj
    }
    return render(request, "base/components/key.html", context) 

In the template have I built it like this

<form action="" method="POST">
            {% csrf_token %}
            <div id="key" >
                <button hx-post="{{key_url}}" hx-target="#key" hx-swap="beforeend" type="submit">Create new key</button>
            </div>
        </form>

I would like it to generate a new key each time the button is pressed and replace the previous one. Does anybody know how I can implement that?

EDIT These are my urls

path('createkey', key_create_view, name='create_key'),
    path('createkey/<int:id>', key_detail_hx_view, name='key')

This is the form

class CreateKeyForm(forms.ModelForm):
    class Meta:
        model = Key
        fields = ()

So it is an empty form, just to create the object.

CodePudding user response:

The key creating form's hx-post endpoint should be the key_create_view not the key displaying view. Actually, on the form page {{ key_url }} is empty. Furthermore, since you want to show only the last generated key, the swapping method should be the default innerHTML, so HTMX will replace the content inside the #key div with the new one.

<form action="" method="POST">
  {% csrf_token %}
  <div >
    <button hx-post="{% url 'create_key' %}" hx-target="#key" type="submit">Create new key</button>
  </div>
</form>

<!-- HTMX will swap the returned key here -->
<div id="key"></div>

Edit:

The duplicate key error is caused by the wrong approach to add a dynamic default value. If you actually call the function (get_random_string(10)) it will be executed once and each key will receive the same random string. You need to pass the function as a reference, so each time Django creates a new model, it will call this function, creating a new random string. Since get_random_string needs a parameter, we need to create a small wrapper function.

def get_default_random_string():
    return get_random_string(length=10)


class Key(models.Model):
    key = models.CharField(max_length=10, null=False, default=get_default_random_string, unique=True)
    amount = models.IntegerField(default=1)
    created = models.DateTimeField(auto_now_add=True)
  • Related