I can see two approaches, the first one is using an extension of your code. And for that example I will just do half of what you want (create the "tag" and update the list). Later explaining why.
forms.py
class AtCreateForm(forms.ModelForm):
class Meta:
...
def __init__(self, *args, **kwargs):
queryset = kwargs.pop('queryset')
super().__init__(*args, **kwargs)
self.fields['apostols'].queryset = queryset
A minor difference form your original form, with addition of one line queryset = kwargs.pop('queryset')
where we pass a queryset as a keyword
to dinamically update it.
views.py
class ApostolsView(View):
queryset = User.objects.all()
form_class = AtCreateForm
template_name = 'apostols.html'
apostols = []
context = {}
def get(self, request):
self.apostols.clear()
form = self.form_class(queryset=self.queryset)
self.context = {
'form': form,
'apostols': self.apostols,
}
return render(request, self.template_name, self.context)
def post(self, request):
apostol_name = request.POST.get('apostols')
csrftoken = request.POST['csrfmiddlewaretoken']
obj = User.objects.get(username=apostol_name)
data = {
'csrfmiddlewaretoken': csrftoken,
'apostols': obj.id
}
query_dict = QueryDict('', mutable=True)
query_dict.update(data)
form = self.form_class(query_dict, queryset=self.queryset)
if form.is_valid():
if form.cleaned_data['apostols'].username not in self.apostols:
self.apostols.append(form.cleaned_data['apostols'].username)
new_form = self.form_class(queryset=self.queryset.exclude(username__in=self.apostols))
self.context = {
'form': new_form,
'apostols': self.apostols,
}
return render(request, self.template_name, self.context)
apostols.html
{% block content %}
<form method="post" action="/original/apostols/">
{% csrf_token %}
<input type="text" list="user-list" name="apostols">
<datalist id="user-list">
{% for user in form.apostols.field.queryset %}
<option value="{{ user.username }}"></option>
{% endfor %}
</datalist>
<input type="submit" value="Submit">
</form>
<span>
{% for apostol in apostols %}
<span class="badge bg-primary" id="{{apostol}}-badge" style="display: flex; align-items: center; justify-content: center; width: fit-content; margin: 10px 10px 0 0;">
<button type="button" onclick="removeBadge()" value="{{apostol}}" class="btn-close" aria-label="Close" style="height: auto;"></button>
{{apostol}}
</span>
{% endfor %}
</span>
<script>
function removeBadge() {
console.log('remove badge element and update queryset.')
}
</script>
{% endblock %}
As you can see so far, it requires a little bit of work around at validating on POST
request, due to the nature of <datalist>
and Django QueryDict.
Anyway, the main pain points that I want to focus on is that we are going to need Javascript to remove the badge
element. Also, included in that action we would have to send an AJAX request to the view to update the list via queryset.
In general you now can see how this would be very inefficient. Unnecessarily overloading the server(s) with requests and queries for every action, also worsening in performance as data on the table grows.
A full solution using Javascript
By using Javascript and AJAX we can lift this load off the server by querying for data only once and work it on client side until it is ready to be submitted.
To diminish the lines of code in one file some functions were moved to external files (also can be thought as a reuse resource). Namely getCookie() used to retrieve csrftoken
into cookie.js
and other two functions into fetch.js
whose purpose is to get and post data.
fetch.js
async function getData(url) {
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
return response.json();
};
async function postData(url, csrftoken, data) {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken
},
body: JSON.stringify(data)
});
return response.json();
};
views.py
def apostols(request):
if request.method == 'POST':
data = json.load(request)
apostols = data.get('apostols')
# Do the deed with 'apostols'
return JsonResponse({'msg': 'The deed is done', 'data': apostols})
return render(request, 'apostols.html')
def user_list(request):
users = list(User.objects.values())
return JsonResponse({'users': users})
apostols.html
{% load static %}
{% block content %}
<div style="display: flex">
<div style="width: auto; margin-right: 20px;">
<input type="text" list="user-list" id="user-choice" name="user-choice">
<datalist id="user-list">
</datalist>
<input type="button" onclick="appendChoice()" value="Add">
<input type="button" onclick="sendApostols()" value="Send List">
</div>
<div style="width: 50%;">
<div id="badge-list" style="display: flex; flex-flow: row wrap"></div>
</div>
</div>
<br>
<script type="text/javascript" src={% static "js/cookie.js" %}></script>
<script type="text/javascript" src={% static "js/fetch.js" %}></script>
<script>
let users = []
let exclude = []
document.addEventListener("DOMContentLoaded", function() {
getData('/user/list/')
.then((data) => {
for (var i in data.users)
users.push(data.users[i].username)
updateUserList(users, exclude)
})
});
function updateUserList(users, exclude) {
datalist = document.getElementById('user-list');
for (var i = 0; i<=users.length-1; i++){
if (!exclude.includes(users[i])) {
var opt = document.createElement('option');
opt.value = users[i];
opt.innerHTML = users[i];
datalist.appendChild(opt);
}
}
};
function appendChoice() {
datalist = document.getElementById('user-list');
var choice = document.getElementById('user-choice');
var badges = document.getElementById('badge-list');
if (users.includes(choice.value)) {
exclude.push(choice.value);
datalist.innerHTML = '';
updateUserList(users, exclude);
badges.innerHTML += `
<span class="badge bg-primary" id="${choice.value}-badge" style="display: flex; align-items: center; justify-content: center; width: fit-content; margin: 10px 10px 0 0;">
<button type="button" onclick="removeBadge(this)" value="${choice.value}" class="btn-close" aria-label="Close" style="height: auto;"></button>
${choice.value}
</span>
`
choice.value = '';
}
};
function removeBadge(elem) {
badge = document.getElementById(elem.parentNode.id);
badge.remove();
const index = exclude.indexOf(elem.value);
exclude.splice(index, 1);
datalist.innerHTML = '';
updateUserList(users, exclude);
};
function sendApostols(){
const url = '/apostols/';
const csrftoken = getCookie('csrftoken');
data = {'apostols': exclude};
postData(url, csrftoken, data)
.then((response) => {
// You can:
// Do an action
console.log(response.msg);
console.log(response.data);
// Reload the page
// location.reload();
// Redirect to another page
// window.location.href = 'http://localhost:8000/my/url/';
});
};
</script>
{% endblock %}
Other useful links:
- Use kwargs in Django form
- Remove value from JS arr
- get parent element id
- Add options to select element
- Fetch API