I want to build Location dropdown list for choose location. It will contain in Profile Edit.
I built 3-way dropdown in jQuery, it does not work good in my template but it works. However, I have an error “Select a valid choice. That choice is not one of the available choices”, when I save form.
Also, when I tried to build 4-way dropdown It has broken at all. I don’t know how to solve this.
Maybe it may build in other approach I will be happy to know it (for example, HTMX).
models.py
class Profile(models.Model):
slug = models.SlugField(unique=True)
user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE)
country = models.ForeignKey(
"Country", on_delete=models.SET_NULL, null=True, blank=True)
state = models.ForeignKey(
'State', blank=True, on_delete=models.SET_NULL, null=True)
city = models.ForeignKey(
"City", on_delete=models.SET_NULL, null=True, blank=True)
street = models.ForeignKey(
'Street', on_delete=models.SET_NULL, null=True, blank=True)
class Country(models.Model):
name = models.CharField(max_length=100, blank=True, null=True)
def __str__(self):
return self.name
class Meta:
ordering = ["-name"]
class State(models.Model):
name = models.CharField(max_length=100, blank=True, null=True)
country = models.ForeignKey(
'Country', on_delete=models.CASCADE, blank=True, null=True)
def __str__(self):
return self.name
class Meta:
ordering = ["-name"]
class City(models.Model):
name = models.CharField(max_length=100, blank=True, null=True)
state = models.ForeignKey(
'State', on_delete=models.CASCADE, blank=True, null=True)
def __str__(self):
return self.name
class Meta:
ordering = ["-name"]
class Street(models.Model):
name = models.CharField(max_length=100, blank=True, null=True)
city = models.ForeignKey(
'City', on_delete=models.CASCADE, blank=True, null=True)
def __str__(self):
return self.name
class Meta:
ordering = ["-name"]
views.py
class ProfileDetailView(DetailView):
model = Profile
template_name = "users/profile.html"
def update_profile_about(request, slug):
if request.method == "POST":
user_form = UserForm(
request.POST, instance=request.user, prefix="user")
profile_form = ProfileForm(
request.POST, request.FILES, instance=request.user.profile, prefix="profile"
)
if all([user_form.is_valid(), profile_form.is_valid()]):
us = user_form.save()
prof = profile_form.save()
prof.save()
messages.success(request, _(
"Your profile was successfully updated!"))
return redirect("profile", slug=slug)
else:
messages.error(request, _("Please correct the error below."))
else:
user_form = UserForm(instance=request.user, prefix="user")
profile_form = ProfileForm(
instance=request.user.profile, prefix="profile")
return render(
request,
"users/profile_update_about.html",
{"user_form": user_form, "profile_form": profile_form, },
)
def load_states(request):
country_id = request.GET.get('country')
states = State.objects.filter(country_id=country_id).order_by('name')
return render(request, 'users/partials/state_dropdown_list_options.html', {'states': states})
def load_cities(request):
state_id = request.GET.get('state')
cities = City.objects.filter(state_id=state_id).order_by('name')
return render(request, 'users/partials/city_dropdown_list_options.html', {'cities': cities})
def load_streets(request):
city_id = request.GET.get('city')
streets = Street.objects.filter(city_id=city_id).order_by('name')
return render(request, 'users/partials/street_dropdown_list_options.html', {'streets': streets})
urls.py
urlpatterns = [
path("profile/<slug:slug>", ProfileDetailView.as_view(), name="profile"),
path('ajax/load-states/', views.load_states, name='ajax_load_states'),
path('ajax/load-cities/', views.load_cities, name='ajax_load_cities'),
path('ajax/load-streets/', views.load_streets, name='ajax_load_streets'),
]
forms.py
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = (
"country",
"state",
"city",
"street",
)
# State
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['state'].queryset = State.objects.none()
if 'country' in self.data:
try:
country_id = int(self.data.get('country'))
self.fields['state'].queryset = State.objects.filter(
country_id=country_id).order_by('name')
except (ValueError, TypeError):
pass
elif self.instance.pk and self.instance.country:
self.fields['state'].queryset = self.instance.country.state_set.order_by(
'name')
# City
self.fields['city'].queryset = City.objects.none()
if 'state' in self.data:
try:
state_id = int(self.data.get('state'))
self.fields['city'].queryset = City.objects.filter(
state_id=state_id).order_by('name')
except (ValueError, TypeError):
pass
elif self.instance.pk and self.instance.state:
self.fields['city'].queryset = self.instance.state.city_set.order_by(
'name')
# Street
self.fields['street'].queryset = State.objects.none()
if 'city' in self.data:
try:
city_id = int(self.data.get('city'))
self.fields['street'].queryset = Street.objects.filter(
city_id=city_id).order_by('name')
except (ValueError, TypeError):
pass
elif self.instance.pk and self.instance.city:
self.fields['street'].queryset = self.instance.city.street_set.order_by(
'name')
profile_update_about.html (HTML when content forms and Location dropdown list )
{% extends 'base.html' %}
{% block content %}
<form method="POST" enctype="multipart/form-data" id="profileForm" data-states-url="{% url 'ajax_load_states' %}"
novalidate>
{% csrf_token %}
<div class="form-group mb-3">
<label for="{{ profile_form.state.id_for_label }}" class="form-label">State:</label>
{{ profile_form.state }}
{% if profile_form.state.errors %}
<div class="alert alert-warning" role="alert">
{% for error in profile_form.state.errors %}
{{error}}
{% endfor %}
</div>
{% endif %}
</div>
<div class="form-group mb-3">
<label for="{{ profile_form.city.id_for_label }}" class="form-label">City:</label>
{{ profile_form.city }}
{% if profile_form.city.errors %}
<div class="alert alert-warning" role="alert">
{% for error in profile_form.city.errors %}
{{error}}
{% endfor %}
</div>
{% endif %}
</div>
{% comment %} <div class="form-group mb-3">
<label for="{{ profile_form.street.id_for_label }}" class="form-label">Street:</label>
{{ profile_form.street }}
{% if profile_form.street.errors %}
<div class="alert alert-warning" role="alert">
{% for error in profile_form.street.errors %}
{{error}}
{% endfor %}
</div>
{% endif %}
</div> {% endcomment %}
</div>
</div>
<div class="d-flex justify-content-center align-items-center mb-3">
<button class="btn btn-outline-primary">
Update
</button>
</div>
</form>
<script>
// State
$("#id_profile-country").change(function () {
var url = $("#profileForm").attr("data-states-url");
var countryId = $(this).val();
$.ajax({
url: '{% url '
ajax_load_states ' %}',
data: {
'country': countryId
},
success: function (data) {
$("#id_profile-state").html(
data);
}
});
});
// City
$("#id_profile-state").change(function () {
var url = $("#profileForm").attr("data-states-url");
var stateId = $(this).val();
$.ajax({
url: '{% url '
ajax_load_cities ' %}',
data: {
'state': stateId
},
success: function (data) {
$("#id_profile-city").html(
data);
}
});
});
// Street
$("#id_profile-city").change(function () {
var url = $("#profileForm").attr("data-states-url");
var cityId = $(this).val();
$.ajax({
url: '{% url '
ajax_load_streets ' %}',
data: {
'city': cityId
},
success: function (data) {
$("#id_profile-street").html(
data);
}
});
});
</script>
{% endblock content %}
state_dropdown_list_options.html
<option value="">---------</option>
{% for state in states %}
<option value="{{ state.pk }}">{{ state.name }}</option>
{% endfor %}