Django and HTMX active search issue - python

I'm trying to make an active search with HTMX in Django, and it's working, but happens that if I delete the search terms in the form the entire page is rendered twice and can`t fix it.
This is ok:
But this happens if I delete the text introduced in the form:
view.py
class SearchView(View):
#staticmethod
def get(request):
search_term = request.GET.get('search', None)
if search_term:
roads = Road.objects.filter(name__contains=search_term).all()[:100]
template = 'search_results.html'
else:
roads = []
template = 'pkes/search.html'
return render(request=request,
template_name=template,
context={
'roads': roads,
})
search.html
{% extends "pkes/base.html" %}
{% block content %}
<form action="/action_page.php">
<label for="search">Carretera</label>
<input class="input"
name="search"
type="search"
placeholder="Ej: EX-118"
hx-get="/search"
hx-trigger="keyup changed delay:500ms, search"
hx-target="#search-results"
hx-swap="innerHTML"/>
<label for="kilometro">Kilómetro</label>
<input class="input" name="kilometro">
<input type="submit" value="Enviar">
</form>
<div id="search-results">
{% include "search_results.html" %}
</div>
{% endblock %}
search_results.html
{% for road in roads %}
<div>
{{ road.name }}
</div>
{% endfor %}
Thanks!!!
Django 4.1.6, Python 3.11.1, HTMX 1.8.5
Reference: rockandnull.com

You have two types of requests: the regular one, where the client expects a full rendered page, and the HTMX one, where the client expects just an HTML fragment. You have to identify the type of a request in a view function and return full or partial rendered template to the client. Currently you try to guess the type of the request based on the length of the search term. So when the search term is cleared you return a full page instead of just a fragment causing the double rendering issue.
In this modified view function we have a new variable is_htmx that identify the request type. We set the template (full or fragment) based on this. Furthermore we execute a search query based on the search term or just return an empty list.
class SearchView(View):
#staticmethod
def get(request):
search_term = request.GET.get('search', None)
is_htmx = self.request.headers.get('HX-Request') == 'true'
template = 'search_results.html' if is_htmx else 'pkes/search.html'
roads = Road.objects.filter(name__contains=search_term).all()[:100] if search_term else []
return render(request=request,
template_name=template,
context={
'roads': roads,
})
You can also use the Django-HTMX package, so you have a request.htmx attribute set by a middleware that identifies the type of the request.

Related

Why is my flask form validation returning Not a valid choice?

I have been trying to figure out why my Flask form will not properly validate my select field choices even though the choices are coming from the select field options.
My assumption is that the select option when passed back from the server is unicode and is being compared to the choice which is a string, however, I thought coerce=str would fix that. I printed out the form data and request data which is the output below. Why isn't it working?
My code is attached below, removed csrf token key from the output dict. It seems like a very simple thing, but I can't figure it out.
forms.py
class PlatformForm(FlaskForm):
platform_options = [('test', 'Test'), ('test2','Test2')]
platforms = wtforms.SelectField('Platforms', choices=platform_options, coerce=str, validators=[DataRequired()])
views.py
#app.route('/', methods=['POST', 'GET'])
def index():
form = forms.PlatformForm()
if form.is_submitted():
print form.data
print request.form
if form.errors:
print form.errors
return render_template('home.html', form=form)
index.html
{% extends "base.html" %}
{% block content %}
<h4>Select a Platform</h4>
<form method="POST">
{{ form.csrf_token }}
<select class="custom-select" name="platform">
{% for value, text in form.platforms.choices %}<br>
<option value="{{ value }}">{{ text }}</option>
{% endfor %}
</select>
<button id="submit_inputs" type="submit" class="btn btn-default">Submit</button>
</form>
{% endblock %}
output
{'platforms': 'None'}
ImmutableMultiDict([('platform', u'test')])
{'platforms': [u'Not a valid choice']}
EDIT:
I figured out the problem. It's the way I'm creating the Select drop down through HTML and Jinja. Iterating through the choices and creating option tags doesn't seem to instantiate anything in the form data itself when passed back into Python. Changing that whole for loop to just
{{form.platforms}}
created a select drop down field that actually works.
You have a name mismatch. In the form, you named your select field platforms (plural). In the HTML, you use platform (singular).
I recommend that instead of manually rendering the fields in your template, you let WTForms generate the HTML for you. For the form label, you can use {{ form.platforms.label }}, and for the actual field {{ form.platforms() }}. You can pass any attributes you want to field to have as keyword arguments.
I think something might be going wrong because of the way you are rendering the form in your html file. If my hunch is right, try this:
{% extends "base.html" %}
{% block content %}
<h4>Select a Platform</h4>
<form method="POST">
{{ form.hidden_tag() }}
Select: {{ form.plaforms}}
{{ form.submit(class="btn btn-default") }}
</form>
{% endblock %}
and then try if form.validate_on_submit() in your views.py file
taken from this stack overflow answer by pjcunningham:
"validate_on_submit() is a shortcut for is_submitted() and validate().
From the source code, line 89, is_submitted() returns True if the form
submitted is an active request and the method is POST, PUT, PATCH, or
DELETE.
Generally speaking, it is used when a route can accept both GET and
POST methods and you want to validate only on a POST request."

How to update multiple objects at once in Django?

As the image presents I have to update multiple objects at once, e.g. to update the status of each object. If the status is active, the object will display on another page and vice versa. The goal is to change the status of one object (or all) with one button.
At the moment if I click on 'Update_all' I got only one value.
Django's Admin Page to take action on multiple objects would be a nice solution, but I have no idea, how this template is constructed although I considered the html code of the template.
Another try I attempted - similiar to the image above - was this one:
My template
<div class="container">
{% if forms.items %}
{% for key,value in forms.items %}
<form class="custom-form-manage-habis" method="post" action="">
{% csrf_token %}
<div class="container custom-div-manage-habits">
{{key}}
 
{{value}}
</div>
<hr>
{% endfor %}
{% else %}
<p>There are no habits to manage.</p>
{% endif %}
<input type="submit" value="Apply changes" {% if not forms.items
%}style="display:none"{% endif %} ></input>
</form>
</div>
...and in my view:
def post(self, request):
print('postFuction')
print(request.POST)
form_collection = {}
habit_update_list = request.POST.getlist('is_active')
print(habit_update_list)
habits = Habit.objects.filter(created_by=request.user.userprofile)
i = 0
for habit in habits:
print('I: ' + str(i))
form = HabitToManageForm(request.POST, instance=habit)
if form.is_valid():
habit = form.save(commit=False)
habit.is_active = habit_update_list[i]
print(habit)
habit.save()
else:
print('Error while check if form is valid')
i += 1
return redirect('manage_habits')
The problem here that I get indeed values of the objects, but unsorted and therefore it may be true that I get a value of object2, which will saved in object4.
So, is there a common practice or best way to handle this problem? Or may someone has any hints how I adopted the django admin template approach "select an object, then change it".
EDIT
Form:
class HabitToManageForm(forms.ModelForm):
class Meta:
model = Habit
fields = ('is_active',)

Can I build my form without using Django form?

I'm using Django and I just did a big form Using HTML5 and bootstrap. Can I still send the form via the post method to django if I'm not using it to generate the form? Should I definitely redo my form using Django?
NOTE: There may be a better way of doing this, if there is I'd really like to know, this is just how I have done it in the past.
You will still need a forms.py file in your app.
In forms.py:
from django import forms
class MyForm(forms.Form):
# FORM FIELDS HERE
Then put the form in the context dictionary for your view:
def myView(request):
if request.method == "POST":
# FORM PROCESSING HERE
else:
myform = MyForm() #create empty form
return render(request, "template.html", {"myform": myForm}
Now in your template you can add:
<form id="myForm" name="myFormName" method="post" action=".">
{% csrf_token %}
{% for field in myform %}
{{ field.as_hidden }}
{% endfor %}
</form>
This will add your django form to the page without displaying it. All of your form inputs are given the id id_fieldName where fieldName is the field name you defined in the forms.py file.
Now when the user clicks your "submit" button (which I am assuming is a bootstrap button given the rest of your form is). You can use Jquery to input the bootstrap field values into those of the hidden form.
Something like:
$("#mySubmitButton").click(function() {
$("#id_djangoFormField").val($("#myBootstrapFormField").val());
$("#myForm").submit();
}
);
This will submit the django form with the inputs from bootstrap. This can be processed in the view as normal using cleaned_data["fieldName"].
A bit late I post the solution I found for including a form in a modal in a class based detail view. Dunno if it's really orthodox but it works.
I don't use any Form Class or Model. (Django 3.9)
Within the template, I send a field value of my object in a hidden div. If this value is missing for a special action (because for the most of actions on the object, it's not required), a modal pops asking for updating the given field. This modal is triggered with JS that check the presence (or not) of the required value.
In the modal, I display a list of radio choices buttons in an ordinary form inviting the user to update the field. The form's action leads to a view that will update the given field.
modal.html
<form action="{% url 'update-sku-column' object.pk %}" method="post">
{% csrf_token %}
{% if csv_headers %}
<div class="m-3 ps-3">
{% for header in csv_headers %}
{% for csv_sample in csv_samples %}
{% if forloop.counter0 == forloop.parentloop.counter0 %}
<div class="form-check">
<input class="form-check-input" type="radio" name="chosen-field" value="{{ forloop.counter0 }}">
<label class="form-check-label" for="{{ forloop.counter0 }}">
<span class="ms-3">{{ header }} </span>: <span class="ms-1 text-secondary">{{ csv_sample }}</span>
</label>
</div>
{% endif %}
{% endfor %}
{% endfor %}
</div>
{% endif %}
<div class="modal-footer">
<button type="submit" class="btn btn-success">Enregistrer</button>
</div>
</form>
urls.py
[...]
path('flow/<int:pk>/update-sku-column',
set_sku_column, name='update-sku-column'),
[...]
views.py
#login_required
def set_sku_column(request, pk):
if request.method == 'POST':
column = request.POST['chosen-field']
flow = Flow.objects.get(pk=pk)
flow.fl_ref_index = column
flow.save()
return redirect('mappings-list', pk=pk)
[...]
Even if I can imagine it's not the best way, it works.
don't forget the {% csrf_token %}otherwise it won't

Django: how to add an action button next to the History button in an admin form - cleanly?

In my Tournament admin, I want to add a button for creating the match structure, which depends on the previously registered competing Teams. That is an action that concerns a single tournament, and has no "result" - it just does a moderately complicated update to the database.
I implemented the functionality as an admin action, it works well, and it seems like best way to implement that functionality. However, most of the time, the user will want to execute that action from the tournament page, instead of from the tournament list.
This answer shows how to add the admin action form to the relevant page, but I don't need the drop down list, which also includes the "delete" action; a simple button would be better.
This question is about adding buttons (though it's for the list page), but the proper styling only works for a href tags, not for buttons; I'm executing an action, not showing a new document. I want to avoid duplicating the relevant CSS to work with a form submit button. I'd also like to avoid hardcoding the action name, or generally repeating the things that the admin action dropdown would infer for me.
So my specific questions are:
What's the best way to get the styling right? Do I have to use an a tag and submit via JavaScript, or is there a better way?
How would I best duplicate the logic that this answer gets by using {% admin_actions %} (generating a form with a dropdown list)? That is, retrieving the admin actions, showing them using their descriptions, etc., but as buttons?
Relevant code follows:
admin.py
# from https://stackoverflow.com/a/20379270/371191
class ActionInChangeFormMixin(object):
def response_action(self, request, queryset):
"""
Prefer http referer for redirect
"""
response = super(ActionInChangeFormMixin, self).response_action(request, queryset)
if isinstance(response, HttpResponseRedirect):
response['Location'] = request.META.get('HTTP_REFERER', response.url)
return response
def change_view(self, request, object_id, form_url='', extra_context=None):
actions = self.get_actions(request)
if actions:
action_form = self.action_form(auto_id=None)
action_form.fields['action'].choices = self.get_action_choices(request)
else:
action_form = None
extra_context = extra_context or {}
extra_context['action_form'] = action_form
return super(ActionInChangeFormMixin, self).change_view(request, object_id, extra_context=extra_context)
class TournamentAdmin(ActionInChangeFormMixin, admin.ModelAdmin):
actions = ['setup_matches']
def setup_matches(self, request, queryset):
for tournament in queryset:
try:
tournament.setup()
except ValueError as ex:
messages.error(request, 'Could not update %s: %s' % (tournament, ex))
else:
messages.success(request, 'Updated %s' % (tournament,))
setup_matches.short_description = 'Setup matches for selected tournaments'
change_form.py
{% extends "admin/change_form.html" %}
{% load i18n admin_urls %}
{% block object-tools-items %}
<li>
<form action="{% url opts|admin_urlname:'changelist' %}" method="POST">{% csrf_token %}
<input type="hidden" name="action" value="setup_matches">
<input type="hidden" name="_selected_action" value="{{ object_id }}">
<button value="0" name="index" title="Setup matches for selected tournaments" type="submit">Setup matches</button>
</form>
</li>
{{ block.super }}
{% endblock %}
Most of the code in ActionInChangeFormMixin.change_view() is specifically setup code for the dropdown list, and thus dead code when used with the template shown above. The logic happens here:
action_form = self.action_form(auto_id=None)
action_form.fields['action'].choices = self.get_action_choices(request)
action_form creates the actual form - which we don't want to render anyway. get_action_choices populates the <select> with tuples to use as options.
To be as flexible as possible, I'll introduce a new method, which retrieves only the actions we want to show. Also, let's get rid of the unnecessary code (the new code is inspired by get_action_choices):
class ActionInChangeFormMixin(object):
# ...
def get_change_actions(self, request):
return self.get_actions(request)
def change_view(self, request, object_id, form_url='', extra_context=None):
actions = self.get_change_actions(request) or OrderedDict()
extra_context = extra_context or {}
extra_context['change_actions'] = [(name, description % admin.utils.model_format_dict(self.opts))
for func, name, description in six.itervalues(actions)]
return super(ActionInChangeFormMixin, self).change_view(request, object_id, extra_context=extra_context)
In TournamentAdmin, we can then filter which actions we want to see. In this case, I don't want to show a button for the bulk delete action:
def get_change_actions(self, request):
result = self.get_actions(request)
del result['delete_selected']
return result
change_form.html now needs some logic to render the relevant buttons:
{% extends "admin/change_form.html" %}
{% load i18n admin_urls %}
{% block object-tools-items %}
{% for action_name, action_description in change_actions %}
<li>
<form id="action_{{ action_name }}" action="{% url opts|admin_urlname:'changelist' %}" method="POST">{% csrf_token %}
<input type="hidden" name="action" value="{{ action_name }}">
<input type="hidden" name="_selected_action" value="{{ object_id }}">
{{ action_description }}
</form>
</li>
{% endfor %}
{{ block.super }}
{% endblock %}
This uses JavaScript for submitting the form; I don't think there's a cleaner way to get the styling right.

Display form input with Django

So basically I want to make a simple form I can enter text and the after I hit submit, see the text.
Here is my forms.py:
class Search(forms.Form):
search = forms.CharField()
Here is my views.py:
def search(request):
context = RequestContext(request)
if request.method == 'POST':
search = Search(data=request.POST)
if search.is_valid():
ticker = search.save()
ticker.save()
success = True
else:
print search.errors
else:
search = Search()
return render_to_response('ui/search.html', {"search":search}, context)
Here is the html form that you use to type in (I'm using bootstrap for styling purposes):
<form class="navbar-form navbar-right" role="search" action="/search/" method="post" name="tick">
{% csrf_token %}
<div class="form-group">
<input type="text" class="form-control" placeholder="Enter stock symbol">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
And finally, I want the text entered in the form to be displayed on "search.html" which looks like this currently:
{% extends 'ui/base.html' %}
{% block title %} search {% endblock %}
{% block body_block %}
<br>
<p>test</p>
{{ form.search.data }} <!--I'm pretty sure this is not correct -->
{% endblock %}
Anyone know how I can do this? Thanks.
Your form name is search.
To render the value with modern django, you need to call the value method of the field, therefore your template should look like the following:
{{ search.search.value }}
Your template is wrong, as you suspect.
It is looking for a context variable named "form", but you have given it a context dictionary with a key named "search".
Also, "data" is the argument that you use to build up your Search object (correctly), but when you want to extract the user's input from it, you should use the field names instead, and you need to call value() on them in order to get the bound value. So, to get the contents of the text field called search, you should use search.search.value.
Try changing the line
{{ form.search.data }}
to
{{ search.search.value }}

Categories