I have a job site where a user enters a zip code into a form and a list of jobs matching that zip code is displayed
search.html :
<h6>Results: {{ number_of_results }}</h6>
{% for job in jobs_matching_query %}
<h2><a class="job_link" href="#">{{ job.job_title}}</a></h2>
<p class="job_address lead">{{ job.establishment_name }} - {{ job.address }}</p>
{% endfor %}
<form action = "{% url 'email_subscription' %}">
<p>Subscribe to recieve job alerts near {{ query }}:</p> <!-- query stores zip code-->
<input type="text" name= "email" placeholder="Email">
<button class="btn" type="submit">Subscribe</button>
</form>
The search form is handled by the following view (not sure if I need to include this or not):
def job_query(request):
if request.method == "GET":
query = request.GET.get('query')
jobs_matching_query = Job.objects.filter(zip_code__iexact = query) | Job.objects.filter(city__iexact=query) | Job.objects.filter(state__iexact=query)
number_of_results = 0
for job in jobs_matching_query:
number_of_results = number_of_results + 1
return render(request, 'core/search.html', {'query': query ,'jobs_matching_query': jobs_matching_query, 'number_of_results': number_of_results})
On that search.html I have an email subscription box (subscribe to recieve alerts for that particular zip code), is there a way to pass the value of query from that page to an email_subscription view? I believe I've seen this done in the url so here is the url for said view
url(r'^email_subscription$', views.email_subscription, name="email_subscription"),
You can use django sesions for that you can save the query in django session
#put it somewhere
request.session['query'] = my_query
#then you can access it from the other view in views.email_subscription
it is going to work but im not sure if this is a practitacal one
Related
Writing an admin action so an administrator can select a template they can use to send a message to subscribers by inputting only the subject and text message. Using a filtered list from the admin panel an action called broadcast is triggered on this queryset (the default filter list). The admin action 'broadcast' is a function of a sub-classed UserAdmin class. The intermediate page is displayed that shows a dropdown selector for the emailtype, the queryset items (which will be email addresses, input fields for the subject and message text (message is required field) a button for optional file attachment followed by send or cancel buttons. Problem 1) after hitting the send button the app reverts to the admin change list page. In the broadcast function, the conditional if 'send' in request.POST: is never called.
forms.py
mail_types=(('1','Newsletter Link'),('2','Update Alert'))
class SendEmailForm(forms.Form):
_selected_action = forms.CharField(widget=forms.MultipleHiddenInput)
#Initialized 'accounts' from Account:admin.py Actions: 'send_email' using>> form = SendEmailForm(initial={'accounts': queryset})
my_mail_type=forms.ChoiceField(label='Mail Type',choices=mail_types,required=False)
subject = forms.CharField(widget=forms.TextInput(attrs={'placeholder': ('Subject')}),required=False)
message = forms.CharField(widget=forms.Textarea(attrs={'placeholder': ('Teaser')}),required=True,min_length=5,max_length=1000)
attachment = forms.FileField(widget=forms.ClearableFileInput(),required=False)
accounts = forms.ModelChoiceField(label="To:",
queryset=Account.objects.all(),
widget=forms.SelectMultiple(attrs={'placeholder': ('user_email#somewhere.com')}),
empty_label='user_email#somewhere.com',
required=False,
admin.py
from .forms import SendEmailForm
from django.http import HttpResponseRedirect,HttpResponse
from django.shortcuts import render, redirect
from django.template.response import TemplateResponse
def broadcast(self, request, queryset):
form=None
if 'send' in request.POST:
print('DEBUGGING: send found in post request')
form = SendEmailForm(request.POST, request.FILES,initial={'accounts': queryset,})
if form.is_valid():
#do email sending stuff here
print('DEBUGGING form.valid ====>>> BROADCASTING TO:',queryset)
#num_sent=send_mail('test subject2', 'test message2','From Team',['dummy#hotmail.com'],fail_silently=False, html_message='email_simple_nb_template.html',)
self.message_user(request, "Broadcasting of %s messages has been started" % len(queryset))
print('DEBUGGING: returning to success page')
return HttpResponseRedirect(request, 'success.html', {})
if not form:
# intermediate page right here
print('DEBUGGING: broadcast ELSE called')
form = SendEmailForm(request.POST, request.FILES, initial={'accounts': queryset,})
return TemplateResponse(request, "send_email.html",context={'accounts': queryset, 'form': form},)
send_email.html
{% extends "admin/base_site.html" %}
{% load i18n admin_urls static %}
{% load crispy_forms_tags %}
{% block content %}
<form method="POST" enctype="multipart/form-data" action="" >
{% csrf_token %}
<div>
<div>
<p>{{ form.my_mail_type.label_tag }}</p>
<p>{{ form.my_mail_type }}</p>
</div>
<div>
<p>{{ form.accounts.label_tag }}</p>
<p>
{% for account in form.accounts.queryset %}
{{ account.email }}{% if not forloop.last %}, {% endif %}
{% endfor %}
</p>
<p><select name="accounts" multiple style="display: form.accounts.email">
{% for account in form.accounts.initial %}
<option value="{{ account.email }}" selected>{{ account }}</option>
{% endfor %}
</p></select>
</div>
<div>
<p>{{ form.subject.label_tag }}</p>
<p>{{ form.subject }}</p>
</div>
<div>
<p>{{ form.message.label_tag }}</p>
<p>{{ form.message }}</p>
</div>
<div>
<p>{{ form.attachment.label_tag }}</p>
<p>{{ form.attachment.errors }}</p>
<p>{{ form.attachment }}</p>
</div>
<input type="hidden" name="action" value="send_email" />
<input type="submit" name="send" id="send" value="{% trans 'Send messages' %}"/>
{% trans "Cancel this Message" %}
</div>
</form>
{% endblock %}
Inspecting the browser at the POST call seems to show all the data was bound. Another poster here suggested the admin action buttons divert requests to an internal 'view' and you should redirect to a new view to handle the POST request. I can't get that to work because I can't get a redirect to 'forward' the queryset. The form used in the suggested fix was simpler and did not use the queryset the same way. I have tried writing some FBVs in Forms.py and Views.py and also tried CBVs in views.py but had issues having a required field (message) causing non-field errors and resulting in an invalid form. I tried overriding these by writing def \_clean_form(self): that would ignore this error, which did what it was told to do but resulted in the form essentially being bound and validated without any inputs so the intermediate page didn't appear. Which means the rabbit hole returned to the same place. The send button gets ignored in either case of FBVs or CBVs, which comes back to the admin action buttons Post requests revert to the admin channels!
Any ideas on how to work around this? Key requirements: From the admin changelist action buttons:
the Form on an intermediate page must appear with the queryset passed from the admin changelist filter.
The message input field on the form is a required field.
the send button on the HTML form view needs to trigger further action.
NOTES: My custom Admin User is a subclass of AbstractBaseUser called Account, where I chose not to have a username and am using USERNAME_FIELD='email'. Also, I do not need a Model.py for the SendEmailForm as I don't need to save the data or update the user models, just send the input message using the chosen template and queryset. Help is much appreciated!
It will never work in your case:
You call the action.
You receive the Action Confirmation template render.
After pressing "SEND" in your "confirmation" step, you send a POST request to ModelAdmin, not in your FB-Action.
ModelAdmin gets a POST request without special parameters and shows you a list_view by default.
In your case, you should add a send_email.html template:
{% load l10n %}
{# any your staff here #}
{% block content %}
<form method="POST" enctype="multipart/form-data">
{# any your staff here #}
<div>
<p>{{ form.attachment.label_tag }}</p>
<p>{{ form.attachment.errors }}</p>
<p>{{ form.attachment }}</p>
</div>
{% for obj in accounts %}
<input type="hidden" name="_selected_action" value="{{ obj.pk|unlocalize }}" />
{% endfor %}
<input type="hidden" name="action" value="broadcast" />
{# any your staff here #}
</form>
{% endblock %}
You should change your action view, some things are not working in your code:
def broadcast(self, request, queryset):
form = SendEmailForm(data=request.POST, files=request.FILES, initial={'accounts': queryset})
if 'send' in request.POST:
... # your staff here
if form.is_valid():
... # your staff here
# return HttpResponseRedirect(request, 'success.html', {} ) this is NEVER WORK
return TemplateResponse(request, 'success.html', {})
... # your staff here
return TemplateResponse(request, "send_email.html",context={'accounts': queryset, 'form': form},)
I am giving you a solution that I have TESTED on my project. I am sure, it works.
We were told on DjangoCon Europe 2022 that django-GCBV is like a ModelAdminAction and I've added a link below for the talk.
https://youtu.be/HJfPkbzcCJQ?t=1739
I can't get that to work because I can't get a redirect to 'forward' the queryset
I have a similar use case and save the primary keys of the filtered query set in the session (in your case you may be able to save emails and avoid another query)
def broadcast(self, request, queryset):
request.session["emails"] = list(queryset.values_list("emails", flat=True))
return HttpResponseRedirect("url_to_new_view")
I can then use primary keys to filter query set in the new view. You also handle the form in this new view.
User.objects.filter(email__in=self.request.session["emails"])
I have a simple app where I want to have a single filter dynamically update the queryset in two different Views at the same time.
In this example, the user can filter based on city/country/continent and I'd like to dynamically update (1) the table showing the relevant objects in the model, and (2) the leaflet map to plot the points.
I think the main issue here is that I need to trigger an update to the filter queryset on multiple 'views' at the same time and not sure how to structure my project to achieve that. Or if that's the right way to think about the problem.
I'm trying to have a filter work with{% for city in cityFilterResults %} iterator in two different views at the same time.
How can I achieve having two different views update based on a Filter using HTMX?
index.html:
(Note: My expected behaviour works if I switch the hx-get URL between either the table or the map. But they don't filter together and this is the issue I'm stuck on.)
<body>
<h3>Filter Controls:</h3><br>
<form hx-get="{% url '_city_table' %}" hx-target="#cityTable">
<!-- <form hx-get="{% url '_leaflet' %}" hx-target="#markers"> -->
{% csrf_token %}
{{ cityFilterForm.form.as_p }}
<button type="submit" class="btn btn-primary"> Filter </button>
</form>
[...]
<!-- Table Body -->
<tbody id="cityTable" hx-get="{% url '_city_table' %}" hx-trigger="load" hx-target="nearest tr"> </tbody>
[...]
<!-- Div to get the markers -->
<div id="markers" hx-get="{% url '_leaflet' %}" hx-trigger="my_custom_trigger from:body"> </div>
</body>
<script>
[... Leaflet stuff ...]
document.getElementById('markers');
</script>
_city_table.html:
{% for city in cityFilterResults %}
<tr>
<td> {{ city.city }} </td>
<td> {{ city.country }} </td>
<td> {{ city.continent }} </td>
<td> {{ city.latitude|floatformat:4 }} </td>
<td> {{ city.longitude|floatformat:4 }} </td>
</tr>
{% endfor %}
_leaflet.html:
<script>
if (map.hasLayer(group)) {
console.log('LayerGroup already exists');
group.clearLayers();
map.removeLayer(group);
} else {
console.log('LayerGroup doesn't exist');
}
{% for city in cityFilterResults %}
var marker = L.marker([{{city.latitude}}, {{city.longitude}}]).addTo(group)
.bindPopup("{{city.city}}")
{% endfor %}
group.addTo(map);
</script>
views.py:
def _city_table(request):
city_filter_form = cityFilter(request.GET, queryset=cityModel.objects.all())
city_filter_results = city_filter_form.qs
context={ 'cityModel': cityModel.objects.all(),
'cityFilterResults': city_filter_results }
response = render(request, '_city_table.html', context)
response['HX-Trigger'] = 'my_custom_trigger'
return response
def _leaflet(request):
city_filter_form = cityFilter(request.GET, queryset=cityModel.objects.all())
city_filter_results = city_filter_form.qs
context={ 'cityModel': cityModel.objects.all(),
'cityFilterResults': city_filter_results }
return render(request, '_leaflet.html', context)
When the hx-get on Filter form points to the URL for _city_table template, then the table filters as expected but the map does not:
When the hx-get on Filter form points to the URL for the _leaflet template, then the map filters but the table does not:
You can find some solution here:
https://htmx.org/examples/update-other-content/
In particular ther are two solutions
swap-oob (
https://htmx.org/examples/update-other-content/#oob )
You have to post your form to the action of your controller.
The action return the two element that will be replaced
<button hx-get="/Form/OutOfBandResponse" hx-swap="none">ok</button>
<div id="A"></div>
<div id="B"></div>
This is the response:
<!-- this will replace the element with id A -->
<div id="A" hx-swap-oob="true">
Joe Smith
</div>
<!-- this will replace the element with id B -->
<div id="B" hx-swap-oob="true">
Antony Queen
</div>
In alternative you can use events. ( https://htmx.org/examples/update-other-content/#events )
So you have to create a handler for a event, then when you submit the form you have to response with a header that fire that event.
When the event fire, the divs react updating their self.
I'm sharing a solution to my question. It works, which is awesome, but I would very much like to understand what alternative approaches could be implemented. My approach feels like a hack and think I'm missing something.
I was able to use a session to pass the filter between the views. Basically, I am using the Filter form on the table. In the 'views' for the table, two things are happening: (1) I create a session and store the filter results, and (2) return a HTTP response with a header that serves as a trigger for the HTMX on the Leaflet part.
In the HTML template, I have the Leaflet URL waiting for the custom HTMX trigger to come back with the table. Then, in the Leaflet views, I take the data from the session.
views.py:
def _city_table(request):
city_filter_form = cityFilter(request.GET, queryset=cityModel.objects.all())
city_filter_results = city_filter_form.qs
request.session['my_city_filter'] = [city.id for city in city_filter_results]
request.session.modified=True
context={ 'cityModel': cityModel.objects.all(),
'cityFilterResults': city_filter_results }
response = render(request, '_city_table.html', context)
response['HX-Trigger'] = 'my_custom_trigger'
return response
def _leaflet(request):
city_filter_results = [cityModel.objects.get(id=id) for id in request.session['my_city_filter']]
context={ 'cityModel': cityModel.objects.all(),
'cityFilterResults': city_filter_results }
return render(request, '_leaflet.html', context)
index.html:
<div id="markers" hx-get="{% url '_leaflet' %}" hx-trigger="my_custom_trigger from:body"> </div>
I'm making a portfolio project where I'm using the Google Books API to do a books search, and the Django Paginator class to paginate the results. I've been able to get search results using a CBV FormView and a GET request, but I can't seem to figure out how to get pagination working for the API response.
The solution I can think of is to append &page=1 to the url of the first search, then pull that param on every GET request and use that to paginate. The problem is, I can't figure out how to append that param on the first search, and I don't know how I'd increment that param value when clicking the pagination buttons.
Here's what I've got now:
Form:
class SearchForm(forms.Form):
search = forms.CharField(label='Search:', max_length=150)
View:
class HomeView(FormView):
template_name = "home.html"
form_class = SearchForm
pageIndex = 0
def get(self, request, *args, **kwargs):
# get submitted results in view and display them on results page. This will be swapped out for an AJAX call eventually
if "search" in request.GET:
# getting search from URL params
search = request.GET["search"]
kwargs["search"] = search
context = super().get_context_data(**kwargs)
# Rest API request
response = requests.get(
f'https://www.googleapis.com/books/v1/volumes?q={search}&startIndex={self.pageIndex}&key={env("BOOKS_API_KEY")}'
)
response = response.json()
items = response.get("items")
# pagination...needs work
paginator = Paginator(items, 2)
page_obj = paginator.get_page(1)
context["results"] = page_obj
return self.render_to_response(context)
else:
return self.render_to_response(self.get_context_data())
Template:
{% extends "base.html" %}
{% block content %}
<form action="/">
{{ form }}
<input type="submit" value="Submit">
</form>
<h1>Books</h1>
<ul>
{% for result in results %}
<li>{{ result.volumeInfo.title }} : {{result.volumeInfo.authors.0}}</li>
{% empty %}
<li>Search to see results</li>
{% endfor %}
</ul>
{% if results %}
<div class="pagination">
<span class="step-links">
{% if results.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ results.number }} of {{ results.paginator.num_pages }}
</span>
{% if results.has_next %}
next
last »
{% endif %}
</span>
</div>
{% endif %}
{% endblock content %}
I also looked at Django REST Framework for this, but the Google Books API response doesn't contain any info on next page, previous page, etc. I've done this kind of pagination in React and it's not difficult, I'm just having trouble adjusting my mental model for how to do this to Django. If anyone could offer some advice on how to make this work, I'd be very grateful.
I have a model with a lot of fields. I only have a few fields I that I want to be required. So instead of the change list super long, I want to have a short change list then have admin actions that can give predefined subsets of the fields.
The initial action takes me to the correct page but when I submit the form it returns me to whatever page I designate, but doesn't update the fields. I am okay with tearing this down starting over again if needed. I think what I really need to know, what do I put in the action="" portion of the html to have the recursion work properly?
I am using django 1.7. I have to obfuscate a lot of my fields as a cya thing since I am working in a heavily information secure field.
Here is my admin.py
class CredentialAdmin(admin.ModelAdmin):
fields = ['reservedBy','reserveto']
list_display = ['reservedBy','reserveto']
class reserveToFormAdmin(forms.Form):
reservedBy = forms.CharField(widget=forms.Textarea, max_length=50)
reserveto = forms.DateTimeField(widget=forms.DateTimeInput)
def reserveCred(self, request, queryset):
form = None
plural = ''
if 'submit' in request.POST:
form = self.reserveToFormAdmin(request.POST)
for f in form.fields:
print f
print form.is_valid()
print form.errors
if form.is_valid():
reservetos = form.cleaned_data['reserveto']
reservedBys = form.cleaned_data['reservedBy']
print "hello"
count = 0
for cred in queryset:
cred.reserveto = reservetos
cred.reservedBy = reservedByss
cred.save()
count += 1
if count != 1:
plural = 's'
self.message_user(request, "Successfully reserved %s cred%s." % (count, plural))
return HttpResponseRedirect(request.get_full_path(),c)
if not form:
form = self.reserveToFormAdmin(initial={'_selected_action' : request.POST.getlist(admin.ACTION_CHECKBOX_NAME)})
return render(request,'admin/reserveCreds.html',{'creds':queryset, 'form':form, 'path':request.get_full_path()})
reserveCred.short_description = "Reserve Selected Creds"
actions = [check_out_a_cred,check_in_a_cred,audit_creds,CompareAudits,reserveCred]
reserveCreds.html
{% extends "admin/base_site.html" %}
{% block content %}
<p>How long and which department to reserver creds:</p>
<form action="{{ path }}" method="post">{% csrf_token %}
{{ form }}
<input type="submit" name="submit" value="submit" />
<input type="button" value = "Cancel" />
</form>
<h2> reserving: </h2>
<ul>
{% for cred in creds %}
<li> {{ cred.userid }} </li>
{% endfor %}
</ul>
{% endblock %}
I'm making a post from a webpage that is generated dynamically from Django DB content. The data is put into a combo box and is supposed to send the customer id back to Django so I can parse some more queries with it and then display only the customers data that was selected.
However instead of retruning my post data it returns the csrf token instead of the posted data.
<form method='post' action='' id='customers'>
{% csrf_token %}
<select>
{% for item in customer %}
<option value={{ item.customer_id }} name='customer_id'>{{ item.customer_name }}</option>
{% endfor %}
<input type = 'submit' value='Edit'>
</select>
{{ post }}
{{post.customer_id}} #doesnt work either.
this is what it returns:
<QueryDict: {u'csrfmiddlewaretoken': [u'CpzKrwmZsmfiiNHngNWDFSNxqUoBykYO']}>
def portal(request):
customers = Customer.objects.all()
if request.method == 'POST':
vm_groups = Vm_group.objects.all()
vms = Vm.objects.all()
selected_customer = request.POST.copy()
#selected_customer_id = selected_customer['customer_id']
post = selected_customer
context = Context({'customer': customers, 'vm_groups':vm_groups, 'vms':vms, 'post':post,
})
return render(request, 'portal.html', context)
else:
context = Context({'customer': customers,})
return render(request, 'portal.html', context)
Also how would I get just the customer_id from the returned data once it works correctly?
selected_customer = request.POST.copy()
selected_customer_id = selected_customer['customer_id']
Like so?
You need to put the name attribute on the select element, not the option.
But you should probably be using Django's forms framework, anyway.
As #Daniel told that, You need to add name field to select tag not for option tag,
<select name='customer_id'>
{% for item in customer %}
<option value={{ item.customer_id }} >{{ item.customer_name }}</option>
{% endfor %}
<input type = 'submit' value='Edit'>
</select>