HTMX for messages from the backend in django - django

I wanted to use HTMX to show messages from django backend after a lot of trial and error I ended up with a working solution, that I want to leave behind for anyone looking for it - also, please feel free to post your suggestions. Unfortunately, besides a little example from the htmx-django package, there is almost no tutorial material available. Be sure to check the example out, as it covers some basics specially for django users!

For HTMX we need a little element somewhere in the DOM that HTMX can work (swap for example) with. Place for example a
<div id="msg">
{% include "app/messages-partial.html" %}
</div>
somewhere on your index.html. Now we want to use this element to fill it with content, if the need arises. Let's say we click a button, that communicates with a view and the answer gets posted. In django the response is created using render:
class TestView(TemplateView):
template_name = "index.html"
def get(self, request, *args, **kwargs):
...
class_code = """class='alert alert-dismissible fade show'"""
msg_str = """testmessage"""
msg_btn = """<button type='button' class='close' data-dismiss='alert'>x</button>"""
msg = mark_safe(f"""<div {classcode}>{msg_str}{msg_btn}</div>""")
return render(request, "app/messages-partial.html", {"msg": msg})
and a corresponding path in urls.py:
path("action/", views.TestView.as_view(), name = "test"),
I created a simple messages-partial.html:
{% if msg %}
{{ msg }}
{% endif %}
So what I wanted is, when I triggered the the view, the {{ msg }} gets replaced (swapped) by HTMX to the content of the response. Therefore I implement the HTMX part somewhere else on the index.html as follows:
<div class="..."
hx-get="/action"
hx-swap="innerHTML"
hx-target="#msg" >
<button class="btn btn-primary">TEST</button>
</div>
The former <div id="msg">...</div> will swap with {{ msg }} (and I included the typical django-messages close button).
Thanks to the htmx discord channel where friendly people shared their knowledge with me.

Related

How can i add a "like" button in a Django class ListView

I am pulling my hair out trying to add a "like" button in my site´s post app, but as i want to add it in a ListView that contains the rest of the posts entries and everyone has the option to be commented I have added a Formixin to do so, so, now i cannot add another form for the like button as it would mean two posts requests....so I am not finding a clear solution... I have read here and there about using AJAX or Json techs but as im new programing im kind of stuck in it... has anyone any tip to offer?
While using AJAX (javascript XHR requests) would be the proper way so the page doesn't need to be refreshed when just clicking a like button, you can do it without AJAX.
HTML
On the HTML side of things, you can have multiple forms (<form>), one for each post, which have a hidden input field that's the post's id. You have set that explicitly in the HTML template, e.g.
{% for post in post_list %}
<h3>{{ post.title }}</h3>
<p>{{ post.summary }}</p>
<form method="post">
{% csrf_token %}
<input type="hidden" value="{{ post.id }}" name="{{ form.id.html_name }}">
<input type="submit">Like</input>
</form>
{% endfor %}
So basically you're reusing the form multiple times, changing the "value" attribute to match the post.
Django Form
Adding the FormMixin to your view is the right step, just use the form_class to a custom LikeForm with just one field that's an IntegerField called id.
View
By adding the FormMixin you get the form_valid() method, which you'll want to override to save the like:
def form_valid(self, form):
id = form.cleaned_data['id']
try:
post = Post.objects.get(id=id)
except Post.DoesNotExist:
raise Http404
post.likes.add(self.request.user) # assuming likes is a m2m relation to user
return redirect('post_list') # this list view
Hopefully I am not so late, I had similar challenges trying to implement the same functionalities on my website.
I came to realize that each button id should be unique (Preferably the post id if blog), but the classes can be the same.
I was able to solve it. Here is an article I wrote on medium recently on the steps I followed to so get this working you can check it out here

Have url with pk or id to redirect to update page in Django error message

I've created an app, and on the CreateView page, the Submit button works fine to create a new S Reference. I also created an error message if the input value matches an existing Reference. I created button in the error message part and tried to link it to update the page to update these reference fields, like primary contact. I tried many options but have not got right code for the argument with pk or id to get individual record update page.
this is the url in error message.
I tried quite few pk, id options, none of them works.
'pk'=self.pk;
{'pk'=self.pk};
object.id
some code as below
models.py
class LNOrder(models.Model):
reference_number = models.CharField(max_length=15,blank=True, null=True, unique=True, error_messages={'unique':"This reference already exists."})
primary_contact = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
urls.py
urlpatterns = [
path('lfcnotifier', LNCreateView.as_view(), name='lnorder_create'),
path('lfcnotifier/<int:pk>', LNDetailView.as_view(), name='lnorder_detail'),
path('lfcnotifier/<int:pk>/update/', LNUpdateView.as_view(), name='lnorder_update'),
]
template
<div class="input-group mb-3">
<div class="input-group-prepend w-225px">
<label class="input-group-text w-100">S Reference</label>
</div>
<input name="reference_number" type="text" class="form-control" placeholder="Enter your S Reference"/>
<button class="btn btn-primary cardshadow " data-toggle="tooltip" title="Click to submit" style="width:200px;" type="submit">submit</button>
{%for field in form %}
{% for error in field.errors %}
{{ error }} Update Request
{% endfor %}
{% endfor %}
Views.py
class LNCreateView(SuccessMessageMixin,LoginRequiredMixin,CreateView):
model = LNOrder
template_name = 'lfcnotifier/lnorder_create.html'
form_class = LNOrderForm
def form_valid(self, form):
form.instance.created_by = self.request.user
return super().form_valid(form)
I expect when users click on Update Request button, it'll open the update page to edit the individual reference.
but I got message "Could not parse the remainder: '=self.pk' from ''pk'=self.pk'".
I get slightly different messages when I try the above different options.
I would like to have the right code for the URL to update the page when the Update Request button is clicked.
Thanks,
Additional background, I only put some of template code here to save space. They are in form section. If I use the following code
Update Request
instead of
Update Request
it can open the full list page without issue. I can go to update page from full list page without issue. But I want to open update page from here directly other than have one more step.
This is all kinds of confused.
For a start, you can't use a string on the left-hand side of an expression, either in pure Python or in Django templates.
But secondly, you don't have anything called self there. What you do have would be passed from the view; however it's not clear from the code you have posted which actual view this is. It doesn't seem to be that CreateView, because you are linking to the update. But assuming it's actually the LNDetailView, and assuming that that actually is a DetailView, you have access to the current object in the template exactly as object.
So you would do:
{% url 'lnorder_update' pk=object.pk %}
However again, this makes no sense to actually do. You can't submit a form via an a. You need a <form> element with a button.

How can i add several copies of one form and submit them with different data? Flask, WTForms

Hellow. I have a document database and flask app that gives me web-based opportunity to see the db's docs, add them and delete. Every doc has only it's number and name.
Usually I add the documents one by one, cause i have the WTForm -
class addDocForm(FlaskForm):
doc_name = StringField('Название документа', validators=[DataRequired()])
doc_number = StringField('Исходящий номер', validators=[DataRequired()])
the .html code -
<form action="" method="post" >
{{ form.hidden_tag() }}
<div class="row">
<label>{{ form.doc_name.label }}</label>
{{ form.doc_name(size=32) }}
</div>
<div class="row">
<label>{{ form.doc_number.label }}</label>
{{ form.doc_number(size=32) }}
</div>
<div class="row">
<button type="submit">Добавить</button>
</div>
</form>
and some /route logic -
#app.route('/add_doc', methods=['GET', 'POST'])
#login_required
def add_doc():
form = addDocForm()
if form.validate_on_submit():
doc = Doc(doc_name=form.doc_name)
if Doc.query.filter_by(doc_name=form.doc_name.data).first() == None:
db.session.add(doc)
db.session.commit()
So I add each document one by one filling this form and submitting it again and again. Now i've been tired. I want to save my energy by reducing number of clicking on submit button. Of course it's a joke, but the question is really about thing like this:
how can i add several copies of this 'addDocForm' on one page, fill the fields of these copies and click submit only once?
Is there any clever way to do that? I want to add for example 5-7 docs at once without the necessity to add them one by one. Let's suppose i've load the page with my form (one form) fill the fields, and than clicked '+' button and there appear another form.. fill the fields-> '+' button .. again. After all click the 'submit' button and all the data from filled fields by turns go to the data base. Is it real? any ideas? p.s. i have an idea on how to make it using clear sql + html + js, without flask-wtforms, sqalchemy and so on.. but i guess this is wrong way cause half of my app is already written using them. ) so many text, don't sure if anyone reach this point.. but still - help me, pls (((((
You could construct a MegaForm using field enclosures.
For example (untested):
from wtforms import StringField, FormField, FieldList
class AddDocForm(FlaskForm):
doc_name = StringField('Название документа', validators=[DataRequired()])
doc_number = StringField('Исходящий номер', validators=[DataRequired()])
class MegaForm(FlaskForm):
documents = FieldList(FormField(AddDocForm), min_entries=7, max_entries=7)
#app.route('/add_doc', methods=['GET', 'POST'])
#login_required
def add_doc():
form = AddDocForm()
if form.validate_on_submit():
for idx, data in enumerate(form.documents.data):
doc = Doc(doc_name=data["doc_name"])
if Doc.query.filter_by(doc_name=data["doc_name"]).first() == None:
db.session.add(doc)
db.session.commit()

How to specify name of a Model Form's submit button name via django-webtest

I'm using django-webtest to automate functional tests for a Django application. One of my ModelForms has multiple submit buttons. The template, using django-crispy-forms, looks like this:
<form action="" method="post">
{% csrf_token %}
<p>
{{ person_form|crispy }}
<br><br>
{{ admin_form|crispy }}
</p>
<button id="SaveButton" type="submit" name="save_data" class="btn btn-lg btn-primary">Save</button>
<button id="VerifyButton" type="submit" name="verify_data" class="btn btn-lg btn-primary">Verify</button>
</form>
When I submit the form manually from the webpage by clicking on the Save button, the request.POST that is passed into the corresponding view method contains the 'save_data' tag that I use to decide what to do in the code.
However, when I create a django-webtest testcase to do the same, the 'save_data' tag is absent, even if I specify it in form.submit() as follows:
def test_schools_app_access_school_admin_record(self):
school_index = self.app.get(reverse('schools:school_index'),
user=self.school_admin)
assert self.school_name in school_index
school_page = school_index.click(self.school_name)
assert 'View School Administrator' in school_page
school_admin_page = school_page.click('View School Administrator')
person_form = school_admin_page.forms[1]
assert person_form['person-name'].value == self.school_admin_name
# TODO: Figure out how to pass name='save_data' while submitting
person_form['person-email'] = self.school_admin_email
response = person_form.submit(name='save_data', value='save')
# Verify that the field has been updated in the database
person = Person.objects.get(name=self.school_admin_name)
assert self.school_admin_email in person.email
How do I get django-webtest to include the name of the submit button in request.POST ?
Also, since I have multiple forms on the same page, I'm currently using response.forms[1] to select the form of interest. But I would like to use the form id instead. I couldn't locate in the Django documentation how to assign the form id (not field id) to a ModelForm. Could someone help me with this?
I'm using Django 1.7, django-webtest 1.7.8, WebTest 2.0.18 and django-crispy-forms 1.4.0.
I figured out that I'd made a typo in my code snippet because of which my code was not working.
In this fragment
person_form['person-email'] = self.school_admin_email
response = person_form.submit(name='save_data', value='save')
I should have value='Save' instead of 'save'.
With this corrected, the response does contain the name 'save_data'.

Add dynamic content

After a new article is posted (in a form via Ajax) on my Django news site, I want to return a link to the article.
To do so, I'm using a Template object that says:
if form.is_valid():
form.instance.user = request.user
new_article = form.save()
success = Template('<div id="panel_input" class="col-lg-4"> <h2 class="text-center"> Success </h2> <p class="text-center"> Your Article has been posted. You can see and edit details by clicking here. </p></div>')
context = Context({"article_id" : new_article.pk})
return HttpResponse(success.render(context))
The urlsConf for this looks like:
...
url(r'^article/(?P<article_id>\d+)/$', views.article, name='article'),
...
The problem is that I get an error because of {% url "article_manager:article" %}/{{ article_id }}. Apparently, I must pass the article_id inside the previous tag, since the urlsConf requires the id parameter.
But I also get an error when I put the second tag inside the first, like this:
{% url "article_manager:article" {{ article_id }} %}
I'm not sure how to accomplish this task, it doesn't seem to work with the tools I have. Does anyone have any suggestions?
Try {% url "article_manager:article" article_id=article_id %}
Maybe a little more explanation is needed: You were calling the template tag right {% url "namespace:name" %}. Remember that some templatetags can take arguments, in the *args, **kwargs form. The args can be any simple expression understood by the template language, including a context variable (no need to add double-braces). The kwargs follow the same rule, and have the form argument=expression. Thus, you can call some template tags with the form {% tag "exp" 1 request number=5 username=user.name %}