Django - How to unit test view managing M2M relationship - django

I have a view that implements a m2m relationship, and I would like to unit test it, which I did not manage yet. The view seems working with the page I defined, but any suggestion is also welcome.
The context is the following: I would like to manage users' groups in a Django app, and, of course, as I need additional fields, I built a model dedicated to user's management in my app. I defined a page with to multiple select boxes, one for the list of users, the other one with users selected to be part of the group. Inbetween are action icons to move users from one group to the others. At the stage, there is no control if a users shall not belong to more than one group, all users that do not belong to the current group are displayed (I assume it's just a question of filtering data).
My page currently looks like this (btw, if you have any suggestion to make titles displayed above the selection boxes, I would also appreciate, even if it's not the topic here.
I would like to unit test the contain of each group and, later on, the impact of adding or removing a user from group.
At this stage, I was just able to check a user in the database is displayed, but actually I have no clue if it is part of one group or another. If I add some rules, such as verifying the user is not already part of a group (or do not propose it to be added to the group in this case), I need to build more precise test, and I do not know yet how to do.
Here is my current working test code:
class TestAdmGroups(TestCase):
def setUp(self):
self.company = create_dummy_company("Société de test")
self.group1 = EventGroup.create_group({"company": self.company, "group_name": "Groupe 1", "weight": 40})
self.group2 = EventGroup.create_group({"company": self.company, "group_name": "Groupe 2", "weight": 60})
# self.group2 = EventGroup.objects.create(company=self.company, group_name="Groupe 2", weight=60)
self.user_staff = create_dummy_user(self.company, "staff", group=self.group1, admin=True)
self.usr11 = create_dummy_user(self.company, "user11", group=self.group1)
self.usr12 = create_dummy_user(self.company, "user12", group=self.group1, admin=True)
self.usr13 = create_dummy_user(self.company, "user13")
self.usr14 = create_dummy_user(self.company, "user14")
self.usr21 = create_dummy_user(self.company, "user21", group=self.group2)
self.usr22 = create_dummy_user(self.company, "user22", group=self.group2)
def test_adm_update_group(self):
self.client.force_login(self.user_staff.user)
url = reverse("polls:adm_group_detail", args=[self.company.comp_slug, self.group1.id])
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "user11")
self.assertContains(response, "user14")
self.assertContains(response, "user21")
I would like to separate results, and ensure user11 is part of the right list, the other values are supposed to be part of the left one.
The view is the following:
def adm_group_detail(request, comp_slug, grp_id=0):
company = Company.get_company(comp_slug)
if grp_id > 0:
current_group = EventGroup.objects.get(id=grp_id)
group_form = GroupDetail(request.POST or None, instance=current_group)
else:
group_form = GroupDetail(request.POST or None)
group_form.fields['all_users'].queryset = UserComp.objects.\
filter(company=company).\
order_by('user__last_name', 'user__first_name')
if request.method == 'POST':
# Convert the string in a list of user IDs
usr_list = [int(elt) for elt in request.POST['group_list'].split('-') if elt != ""]
group_form.fields['users'].queryset = UserComp.objects.filter(id__in=usr_list).\
order_by('user__last_name', 'user__first_name')
group_form.fields['all_users'].queryset = UserComp.objects.exclude(id__in=usr_list)
if group_form.is_valid():
if grp_id == 0:
# Create empty group
group_data = {
"company": company,
"group_name": group_form.cleaned_data["group_name"],
"weight": group_form.cleaned_data["weight"],
}
new_group = EventGroup.create_group(group_data)
else:
# Update group
new_group = group_form.save()
# Remove all users
group_usr_list = UserComp.objects.filter(eventgroup=new_group)
for usr in group_usr_list:
new_group.users.remove(usr)
# Common part for create and update : add users according to new/updated list
for usr in usr_list:
new_group.users.add(usr)
new_group.save()
# Update form according to latest changes
group_form.fields['all_users'].queryset = UserComp.objects.\
exclude(id__in=usr_list).\
order_by('user__last_name', 'user__first_name')
group_form.fields['group_list'].initial = "-".join([str(elt.id) for elt in new_group.users.all()])
return render(request, "polls/adm_group_detail.html", locals())
I managed to make the view work with both lists being part of the same form, but I can change this if you have any suggestion.
With this view, I noticed that I could get values from one list or another the following way: response.context["group_form"]["all_users"] or response.context["group_form"]["users"] but unfortunately, it looks like it's not possible to enter one of these values as parameter of assertContains() (self.assertContains(response.context["group_form"]["users"], self.user11.user.username) does not work, as the fist parameter is supposed to be a response) nor assertInHTML(), in this case I have the following error message with the same previous parameters:
======================================================================
ERROR: test_adm_update_group (polls.tests_admin.TestAdmGroups)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\Mes documents\Informatique\Developpement\Votes AG\projet_votes\polls\tests_admin.py", line 264, in test_adm_update_group
self.assertInHTML(response.context["group_form"]["users"], self.usr11.user.username)
File "C:\Users\Christophe\.virtualenvs\projet_votes-onIieQ0I\lib\site-packages\django\test\testcases.py", line 791, in assertInHTML
needle = assert_and_parse_html(self, needle, None, 'First argument is not valid HTML:')
File "C:\Users\Christophe\.virtualenvs\projet_votes-onIieQ0I\lib\site-packages\django\test\testcases.py", line 62, in assert_and_parse_html
dom = parse_html(html)
File "C:\Users\Christophe\.virtualenvs\projet_votes-onIieQ0I\lib\site-packages\django\test\html.py", line 220, in parse_html
parser.feed(html)
File "c:\program files\python37\Lib\html\parser.py", line 110, in feed
self.rawdata = self.rawdata + data
TypeError: can only concatenate str (not "BoundField") to str
----------------------------------------------------------------------
As you can see in the screenshot, I would like to check a user is in one list or another, not only displayed on the page like I did yet.
Here is the model's definition:
class EventGroup(models.Model):
"""
Groups of users
The link with events is supported by the Event
(as groups can be reused in several Events)
"""
company = models.ForeignKey(
Company, on_delete=models.CASCADE, verbose_name="société"
)
users = models.ManyToManyField(UserComp, verbose_name="utilisateurs", blank=True)
group_name = models.CharField("nom", max_length=100)
weight = models.IntegerField("poids", default=0)
def __str__(self):
return self.group_name
class Meta:
verbose_name = "Groupe d'utilisateurs"
verbose_name_plural = "Groupes d'utilisateurs"
#classmethod
def create_group(cls, group_info):
new_group = EventGroup(company=group_info["company"], group_name=group_info["group_name"], weight=group_info["weight"])
new_group.save()
return new_group
If it can help, here is the HTML code:
{% extends './base.html' %}
{% load static %}
{% block content %}
<div class="row">
{% include "./adm_head.html" %}
<div class="col-sm-9">
<input type="hidden" id="menu_id" value="3" /> <!-- Hidden value to store the current selected menu -->
<div class="row">
<div id="admin-groups" class="col-sm-12 text-center">
<h4 class="mt-5">Détails du groupe</h4>
</div>
</div>
<div class="row">
<div class="col-sm-12 mt-30">
{% if grp_id %}
<form action="{% url 'polls:adm_group_detail' company.comp_slug grp_id %}" method="post">
{% else %}
<form action="{% url 'polls:adm_group_detail' company.comp_slug %}" method="post">
{% endif %}
{% csrf_token %}
<div class="row">
<div class="control-group {%if group_form.group_name.errors %}error{%endif%}"></div>
<div class="control-group {%if group_form.weight.errors %}error{%endif%}"></div>
{{ group_form.group_name}} {{ group_form.weight }}
<a type="button" id="disp_detail" class="collapse-group btn btn-sm" href="">
<span id="btn_grp" class="fas fa-chevron-up" data-toggle="tooltip" title="Masquer/Afficher détails"></span>
</a>
</div>
<div class="row mt-30 grp-content" id="grp_content">
<div class="col-md-5 d-flex justify-content-center">
<p>Utilisateurs</p>
{{ group_form.all_users}}
</div>
<div class="col-md-2 d-flex flex-column text-center justify-content-around">
<a type="button" id="add_all" class="update-user btn btn-sm" href="">
<span class="fa fa-fast-forward" style="color: #339af0;" data-toggle="tooltip" title="Ajouter tout"></span>
</a>
<a type="button" id="add_selected" class="update-user btn btn-sm" href="">
<span class="fa fa-step-forward" style="color: #339af0;" data-toggle="tooltip" title="Ajouter sélection"></span>
</a>
<a type="button" id="remove_selected" class="update-user btn btn-sm" href="">
<span class="fa fa-step-backward" style="color: #339af0;" data-toggle="tooltip" title="Retirer sélection"></span>
</a>
<a type="button" id="remove_all" class="update-user btn btn-sm" href="">
<span class="fa fa-fast-backward" style="color: #339af0;" data-toggle="tooltip" title="Retirer tout"></span>
</a>
</div>
<div class="col-md-5 d-flex justify-content-center">
<p>Utilisateurs sélectionnés</p><br>
{{ group_form.users }}
<div class="control-group {%if group_form.users.errors %}error{%endif%}"></div>
</div>
</div>
<div class="row">
<div class="col-sm-12 mt-30 text-center">
<button id='upd_grp' class="btn btn-success" type="submit">{% if grp_id %}Mettre à jour{% else %}Créer{% endif %}</button>
&nbsp &nbsp &nbsp
<a class="btn btn-secondary back_btn" href="*">Annuler</a>
</div>
</div>
<div class="row">
<div hidden>
<!-- Liste des ID du groupe -->
{{ group_form.group_list }}
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

I think I found the answer I was looking for and a way to make my tests.
The ideas was to find a way to separate each forms' contains, and it's done thanks to the right application of attributes: isolate the main form as a key of context dict, then use the fields attribute to filter and finally apply the queryset attribute to be able to manage related data accordingly.
Then, the question was : 'how to make the comparison with this specific format?'. I found the answer by filtering on this object, taking advantage that .filter() will retrieve an empty list if no value is found, whereas a .get() would have raised an error.
I will not make this answer the right one until I'm finished or I receive some comments or other ideas for better solution, but the following code for unit tests works very fine for me:
class TestAdmGroups(TestCase):
def setUp(self):
self.company = create_dummy_company("Société de test")
self.group1 = EventGroup.create_group({"company": self.company, "group_name": "Groupe 1", "weight": 40})
self.group2 = EventGroup.create_group({"company": self.company, "group_name": "Groupe 2", "weight": 60})
# self.group2 = EventGroup.objects.create(company=self.company, group_name="Groupe 2", weight=60)
self.user_staff = create_dummy_user(self.company, "staff", group=self.group1, admin=True)
self.usr11 = create_dummy_user(self.company, "user11", group=self.group1)
self.usr12 = create_dummy_user(self.company, "user12", group=self.group1, admin=True)
self.usr13 = create_dummy_user(self.company, "user13")
self.usr14 = create_dummy_user(self.company, "user14")
self.usr21 = create_dummy_user(self.company, "user21", group=self.group2)
self.usr22 = create_dummy_user(self.company, "user22", group=self.group2)
def test_adm_update_group(self):
self.client.force_login(self.user_staff.user)
url = reverse("polls:adm_group_detail", args=[self.company.comp_slug, self.group1.id])
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
group_users = response.context["group_form"].fields["users"].queryset
test_user = group_users.filter(id=self.usr11.id)
self.assertEqual(len(test_user), 1)
test_user = group_users.filter(id=self.usr14.id)
self.assertEqual(len(test_user), 0)

Related

How to add data to Django's database by the click of a button using JS and AJAX

I'm trying to add data that I rendered on a page from an API endpoint, to my database when I click "Add to my records" button, as can be seen in the image below, and I'm only trying to store "Date and Country" into the database (my model table has only date and country)
enter image description here
I've seen many resources talking about how JS and AJAX are useful in this case but I'm lost with logic of it all. Is there any way someone could explain how it's supposed to be done.
models.py
from django.db import models
class CountryData(models.Model):
country = models.CharField(max_length=100)
date = models.DateTimeField()
def __str__(self):
return self.country
views.py
def all_countries(request):
first_response = requests.get("https://api.covid19api.com/summary").json()
results = len(first_response["Countries"])
my_new_list = []
data_list = []
for i in range(0, results):
my_new_list.append(first_response["Countries"][i])
# print(my_new_list)
if request.method == "POST":
if request.POST.get("country") and request.POST.get("date"):
added_record = CountryData()
added_record.country = request.POST.get("country")
# 2022-12-19T08:53:48.179Z
added_record.date = datetime.datetime.strptime(
request.POST.get("date"), "%Y-%m-%dT%I:%M:%S.%fZ"
)
added_record.save()
return render(request, "allcountries.html")
else:
return render(request, "allcountries.html", )
context = {"my_new_list": my_new_list}
return render(request, "allcountries.html", context)
urls.py
from django.urls import path, include
from .views import home, all_countries
urlpatterns = [
path("", home, name="home"),
path("allcountries", all_countries, name="all_countries")
]
allcountries.html
{% extends '_base.html' %}
{% block page_title %} Covid19 Statistics For All Countries {% endblock %}
{% block content %}
<h3 class="text-center">Covid19 Statistics For All Countries </h3>
{% for object in my_new_list %}
<div class="row justify-content-center">
<div class="col-sm-10 d-flex justify-content-center">
<div class="card text-dark text-center" style="width: 20rem;">
<div class="card-block card-body">
<form method="POST" action="">
{% csrf_token %}
<h5 class="card-header" name="country">Country: {{object.Country}}, {{object.CountryCode}}</h5>
<br>
<p class="card-text">Total Confirmed Cases: {{object.TotalConfirmed}} </p>
<p class="card-text">Total Deaths Cases: {{object.TotalDeaths}} </p>
<p class="card-text">Total Recovered Cases: {{object.TotalRecovered}} </p>
<p class="card-text" name="date">Date: {{object.Date}}</p>
<button class="btn btn-success" type="submit">ADD TO MY RECORDS </button>
</form>
</div>
</div>
<br>
</div>
</div>
{% endfor %}
{% endblock %}
You can use the following ajax snippet to send data in the backend:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script>
$.ajax({
type: 'POST',
url: 'url', #leave blank if URL is same as the current one
data: {
'country': 'county_name',
'date': 'date_value',
},
success: function(response) {
# depending upon the response from the server you can make an alert here.
},
error: function(response){
# If some error occurred on the backend you can show error message from here
}
});
</script>

how to open only one collapse button inside for loop in django

I am working on a practice project for a weather app, showing the next 7 days of weather forecast when clicking the button in a current weather bootstrap5 card.
Using bootstrap collapse shows every city's daily weather forecasts at once. which part should I change and I haven't learned javascript yet.
The next 7days of the weather forecast list(city_weather_forecast) is placed in the current city weather status(city_weather_data) so that the current city's latitude and longitude can be used to get info for a daily forecast.
views.py/ parts for the context to provide templates.
def index(request):
current_weather_url = "http://api.openweathermap.org/data/2.5/weather?q={}&units=metric&appid={}"
forcast_weather_url = "https://api.openweathermap.org/data/2.5/onecall?lat={}&lon={}&units=metric&exclude=current,minutely,hourly&appid={}"
api_key = "3305a1cea0761d956da3c5f87c21b563"
message = ''
err_msg = ''
message_class = ''
form = CityForm()
if request.method == 'POST':
form = CityForm(request.POST)
if form.is_valid():
new_city = form.cleaned_data['name']
count_city_in_data = City.objects.filter(name=new_city).count()
if count_city_in_data == 0:
r = requests.get(current_weather_url.format(new_city, api_key)).json()
if r["cod"] == 200:
form.save()
return redirect('weather:home')
else:
err_msg = "The city name is not a proper name."
else:
err_msg = "The city is already in your list."
if err_msg:
message = err_msg
message_class = 'alert-danger'
else:
message = 'The city is added successfully.'
message_class = 'alert-success'
cities = City.objects.all().order_by('-created_date')
weather_data = []
for city in cities:
# Current weather status
current_r = requests.get(current_weather_url.format(city, api_key)).json()
# print("current_r : ", current_r["dt"])
# print(forcast_r["daily"][1]["dt"])
# print(len(forecast_r["daily"])) including today, total 8 days of weather forecasts.
# Forecast next 7 days.
city_weather_forecast = []
forecast_r = requests.get(forcast_weather_url.format(current_r["coord"]["lat"], current_r["coord"]["lon"], api_key)).json()
daily_weather_7days = forecast_r["daily"][1:] # excluding today.
for daily_weather in daily_weather_7days:
# Convert Timestapt to human readable time.
date = datetime.utcfromtimestamp(daily_weather["dt"]).strftime("%m/%d")
day = datetime.utcfromtimestamp(daily_weather["dt"]).strftime("%A")
city_weather_forcast_data = {
'date': date,
'day': day,
'icon': daily_weather["weather"][0]["icon"],
'min': daily_weather["temp"]["min"],
'max': daily_weather["temp"]["max"],
}
city_weather_forecast.append(city_weather_forcast_data)
# Include daily forecasts and current weather status and give this info to templates.
city_weather_data = {
'city': city.name,
'country': current_r["sys"]["country"],
'description': current_r["weather"][0]["description"],
'icon': current_r["weather"][0]["icon"],
'temp': current_r["main"]["temp"],
'lat': current_r["coord"]["lat"],
'lon': current_r["coord"]["lon"],
'forecasts': city_weather_forecast,
}
weather_data.append(city_weather_data)
context = {
'weather_data': weather_data,
'form': form,
'message': message,
'message_class': message_class,
}
return render(request, 'weather/index.html', context)
templates
{% if weather_data %}
<div class="delete-all">
<div class="delete-all-button">
<button class="btn btn-danger"><i class="delete-element glyphicon glyphicon-trash"></i> Delete All</button>
</div>
</div>
{% for city in weather_data %}
<div class="container-weather-card">
<div class="card card-bg">
<div class="card-body">
<img class="card-img" src="http://openweathermap.org/img/wn/{{ city.icon }}.png" alt="image">
<h2 class="card-title">{{city.city}}, <span class="card-subtitle">{{ city.country }}</span></h2>
<h4 class="card-temp">{{ city.temp }} °C</h4>
<p class="card-text">{{ city.description }}</p>
<button class="btn btn-position btn-outline-danger"><i class="delete-element glyphicon glyphicon-trash"></i><a class="delete-element" href="{% url 'weather:delete_city' city.city %}"> Delete</a></button>
<button class="btn btn-outline-primary btn-forecast" data-bs-toggle="collapse" href="#weather-7days" role="button" aria-expanded="false" aria-controls="weather-7days"><span>Next 7days</span></button>
</div>
</div>
</div>
{% for city_forecast in city.forecasts %}
<div class="collapse" id="weather-7days">
<div class="container-weather-forecast card">
<div class="card-body">
<div class="row">
<div class="col-md card-section">
<p>{{ city_forecast.date }}</p>
<p>{{ city_forecast.day}}</p>
</div>
<div class="col-md">
<img class="forcast-img" src="http://openweathermap.org/img/wn/{{ city_forecast.icon }}.png" alt="image">
</div>
<div class="col-md card-section">
<p>Low {{ city_forecast.min }} °C</p>
<p>High {{ city_forecast.max }} °C</p>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
{% endfor %}
{% endif %}

Django Crispy Forms - Checkbox not being displayed

I'm trying to add a boolean field on my form and render it with crispy forms tags. And everything is showed except for the checkbox.
My project works with django 2.1, python 3.6 and Bootstrap-4. My version of Django Crispy Forms is: 1.7.2
The field is investment.
Model field not working:
investment = models.BooleanField(default=False, help_text='Want to accept Investments?')
My form:
class CreateProjectForm(forms.ModelForm):
class Meta:
model = Project
fields = ('name', 'short_description', 'category', 'investment')
widgets = {
'name': forms.TextInput(attrs={'placeholder': 'enter the project name here...'}),
'short_description': forms.Textarea(attrs={'rows': '2', 'maxlength': '135', 'class': 'textarea-limited',
'placeholder': 'enter a short description of your project limited to 135 characters'}),
}
def __init__(self, *args, **kwargs):
# first call parent's constructor
super(CreateProjectForm, self).__init__(*args, **kwargs)
# there's a `fields` property now
self.fields['investment'].required = True
self.fields['investment'].widget = forms.CheckboxInput()
self.fields['name'].widget = forms.TextInput(
attrs={'placeholder': 'enter the project name here...'})
self.fields['short_description'].widget = forms.Textarea(
attrs={'rows': '2',
'maxlength': '135',
'class': 'textarea-limited',
'placeholder': 'enter a short description of your project limited to 135 characters'})
# evade all labels and help text to appear when using "as_crispy_tag"
self.helper = FormHelper(self)
self.helper.form_show_labels = False
self.helper._help_text_inline = True
My View:
class ProjectCreateView(SuccessMessageMixin, generic.CreateView):
template_name = 'webplatform/project_create_form.html'
model = Project
form_class = CreateProjectForm
success_message = 'Project created! Now try to add all the details and then publish it!'
def get_success_url(self):
return reverse_lazy('project-edit-general', args=(self.object.id,))
# Set field as current user
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.start_date = timezone.now()
form.instance.history_change_reason = 'Project Created'
return super(ProjectCreateView, self).form_valid(form)
And for the template I tried two methods, but none of them worked:
My template 01:
This is the method I want to use at the end. Displaying each fiend individually so I can make the layout directly on the template.
...
{% load crispy_forms_tags %}
...
<form id="my_form" method="post" enctype="multipart/form-data" novalidate>
{% csrf_token %}
<div class="row">
<div class="col-md-5 col-sm-5">
<h6>Name
<span class="icon-danger">*</span>
</h6>
{{ form.name|as_crispy_field }}
<h6>Categories
<span class="icon-danger">*</span>
</h6>
{{ form.category|as_crispy_field }}
</div>
<div class="col-md-7 col-sm-7">
<h6>Short Description
<span class="icon-danger">*</span>
</h6>
{{ form.short_description|as_crispy_field }}
<h5>
<small>
<span id="textarea-limited-message" class="pull-right">135 characters left</span>
</small>
</h5>
<h6>Investment
<span class="icon-danger">*</span>
</h6>
{{ form.investment|as_crispy_field }}
</div>
</div>
<div class="row buttons-row">
<div class="col-md-4 col-sm-4">
<button type="submit" class="btn btn-outline-primary btn-block btn-round">Create</button>
</div>
</div>
</form>
My template 02:
This one is using directly the {{ form|crispy }} to show all the elements automatically.
...
{% load crispy_forms_tags %}
...
<form id="my_form" method="post" enctype="multipart/form-data" novalidate>
{% csrf_token %}
{{ form|crispy }}
<div class="row buttons-row">
<div class="col-md-4 col-sm-4">
<button type="submit" class="btn btn-outline-primary btn-block btn-round">Create</button>
</div>
</div>
</form>
I checked the rendered HTML and I found where the problem seems to be, but I don't know how to solve it:
Crispy forms create an element adding the corresponding divs and other necessary elements, like for example on the name input:
<div id="div_id_name" class="form-group">
<label for="id_name" class="col-form-label requiredField">Name
<span class="asteriskField">*</span>
</label>
<div class="">
<input type="text" name="name" placeholder="enter the project name here..." class="textinput textInput form-control" required="" id="id_name">
<small id="hint_id_name" class="form-text text-muted">Add a title to your project.</small>
</div>
</div>
But for the checkbox creates the following structure:
<div class="form-group">
<div id="div_id_investment" class="form-check">
<label for="id_investment" class="form-check-label requiredField">
<input type="checkbox" name="investment" class="checkboxinput form-check-input" required="" id="id_investment">
Investment
<span class="asteriskField">*</span>
</label>
<small id="hint_id_investment" class="form-text text-muted">Want to accept Investments?</small>
</div>
</div>
As you can see, the div with the id is inside another div. Well, so if I delete the class of this extra div (form-group) and change the class of the div with id: form-check for form-group, the checkbox appears and is fully functional.
So my idea is try to change the template that crispy forms created for the checkbox, But I don't know how to do that. Also, if there is another better option, I'm open to them.
Try to change self.fields['investment'].required = False, 'cause it seems that because the field is not specified as optional Django ignores it.
Their is an open issue in django-crispy-forms github see : https://github.com/django-crispy-forms/django-crispy-forms/issues/787
According to this the issue is the way bootsrap handle this form element in the last version.
Here is a js hack to make it work :
$(document).ready(function() {
$( ":checkbox" ).each(function( index ) {
$(this).prependTo($("#div_" + $(this).attr('id')))
});
});
I could make a half hack for this by the element I needed by using the following JS:
$(window).on('load',function(){
$('#div_id_investment').addClass('form-group').removeClass('form-check');
});
But this just let the user see and interact with the checkbox, it does not solve that if the field is required and you don't click it, the error message it doesn't appear.
If anyone have an improved JS to make this to all checkboxes and solve the problem with the error message, I'll appreciate it.
note: I'm not marking this as a valid answer for the question because the bug still there and this 'hack' doesn't solve all the problems.

Django - Taking of object model by select tag and passing this object to another view

I'm struggling with the following problem. In my project I have the following model:
models.py
class InputSignal(models.Model):
name = models.CharField(max_length=512)
author = models.ForeignKey(User, on_delete=models.CASCADE)
adnotations = models.TextField(blank=True, null=True)
input_file = models.FileField(upload_to='signals/', null=False, validators=[validate_file_extension])
add_date = models.DateTimeField(default=datetime.now())
last_edit_date = models.DateTimeField(default=datetime.now())
last_json_results = models.FileField(upload_to='resuts/')
objects = models.Manager()
def delete(self):
self.input_file.delete()
super().delete()
def __str__(self):
return self.name
def add_date_pretty(self):
return self.add_date.strftime('%b %e %Y')
Two url addresses:
urls.py
path('display/list', displayviews.display_list, name='display-list'),
path('display/details/<int:signal_id>', displayviews.display_details, name='display-details'),
And two view functions:
views.py
def display_list(request):
signals = InputSignal.objects.filter(author=request.user)
return render(request, 'display_list.html', {'signals': signals})
def display_details(request, signal_id):
signal = get_object_or_404(InputSignal, pk=signal_id)
The template of the first function of the view at this moment looks like this:
display_list.html
<div class="row mt-2">
<div class="col-lg-1"></div>
<div class="col-lg-10">
<select class="form-control mt-2 text-center">
{% for signal in signals %}
<option>
<h2>{{ signal.name }}</h2>
</option>
{% endfor %}
</select>
</div>
<div class="col-lg-1"></div>
</div>
<div class="row mt-3 mb-2">
<div class="col-lg-4"></div>
<div class="col-lg-4">
Perform Analysis
</div>
<div class="col-lg-4"></div>
</div>
I would like based on the structure of this template to design a solution that after selecting the signal name from the select tag and clicking the 'perform analysis' button, go to the next view - display_details (request, signal_id). Where I will save the previously selected model object to the variable. Choosing the right object I would like to use the object ID. I would like to ask for help, what I was able to design presented above.
A couple of things here.
To submit data from a web page you need a form element (unless you're using Ajax, which doesn't seem necessary here). Your select box needs a name attribute, and each option needs a value to submit. Also, you can't submit a form with an a link; you need a submit button. So:
<form action="{% url 'display-details' %}">
<select name="signal_id" class="form-control mt-2 text-center">
{% for signal in signals %}
<option value="{{ signal.id }}">
<h2>{{ signal.name }}</h2>
</option>
{% endfor %}
<button class="btn btn-outline-success btn-block">Perform Analysis</button>
</form>
Now this data is being submitted in the querystring to your display_details view, eg "/display/details/?signal_id=5". So you need to remove the parameter from the URL pattern, and get the data inside the view from the GET params:
path('display/details/', displayviews.display_details, name='display-details'),
...
def display_details(request):
signal_id = request.GET['signal_id']
signal = get_object_or_404(InputSignal, pk=signal_id)

How do I retrieve data to modelForm using Ajax in Django

I want to retrieve data to modelForm based on user input. I am developing a transaction app, I want to retrieve account balance to the form based on the account number selected by the user. First I want to print an alert to test the existence of the account number but keep getting 500 internal server error.
Here is the code
models.py
class Account(models.Model):
ACCOUNT_TYPE = (
('Checking','Checking'),
('Savings','Savings'),
)
user = models.ForeignKey('auth.User', on_delete=models.CASCADE)
account_number = models.PositiveIntegerField(primary_key=True)
account_type = models.CharField(max_length=8, choices=ACCOUNT_TYPE, default='select')
account_balance = models.PositiveIntegerField(blank=True, default=0)
views.py
#login_required
def validate_account_number(request):
account_number = request.Get.get['account_number']
data = {
'is_exist': Account.objects.filter(
account_number=account_number).exists()
}
return JsonResponse(data)
transaction.html
<form class="form-horizontal" method=POST id="sendform">
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade active show" transfer="home">
<div class="col-md-6">
<div class="tile">
<h3 class="tile-title">Transfer Money</h3>
<div class="tile-body">
{% csrf_token %}
{% bootstrap_form form%}
{{ form.non_field_errors }}
<div class="tile-footer">
<div class="row">
<div class="col-md-8 col-md-offset-3">
<button type="submit"> <i class="fa fa-fw fa-lg
fa-check-circle"></i>Send</button> <a class="btn btn-
secondary" href="#"><i class="fa fa-fw fa-lg fa-times-circle">
</i>Cancel</a>
</div>
</div>
</div>
</div>
</div>
</div></div>
</div>
</form>
</main>
{% endblock %}
{% block javascript %}
<script>
$("#id_creditor_account").change(function(){
var account_number = $(this).val();
$.ajax({
url: '{% url 'users:validate_account_number'%}',
data:{
'account_number':account_number
},
dataType: 'json',
success: function(data){
if (data.is_exist){
alert("Account exist");
}
}
});
});
</script>
Here is the screenshot: html_transction_page
Error from console
jquery-3.2.1.min.js:4 GET http://127.0.0.1:8000/users/ajax/validate_account_number/?account_number=5645786809 500 (Internal Server Error)
send # jquery-3.2.1.min.js:4
ajax # jquery-3.2.1.min.js:4
(anonymous) # (index):265
dispatch # jquery-3.2.1.min.js:3
q.handle # jquery-3.2.1.min.js:3
ListPicker._handleMouseUp
The attribute is request.GET, not request.Get. And as Ralf says, it is a method. So either:
account_number = request.GET.get('account_number')
or
account_number = request.GET['account_number']
I think the error is in your view: the method GET.get cannot be indexed, but it can be called.
Try changing
# with brackets, capitalized 'Get'
account_number = request.Get.get['account_number']
to
# with parenthesis, all uppercase 'GET'
account_number = request.GET.get('account_number')
EDIT
Ah, right, its request.GET (all uppercase), not request.Get (capitalized).