mynamenc94

Untitled

Nov 3rd, 2024
59
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 16.05 KB | None | 0 0
  1. Relevant code for the forum question
  2.  
  3.  
  4.  
  5.  
  6. forms.py
  7. class SimulationForm(forms.ModelForm):
  8. class Meta:
  9. model = Simulation
  10. fields = ["model", "name", "description", "ran_by"]
  11. widgets = {
  12. "name": forms.TextInput(attrs={"size": 40, "placeholder": "Input a reference name"}),
  13. "description": forms.Textarea(
  14. attrs={"placeholder": "Give some context to the simulation you are about to create here"}
  15. ),
  16. }
  17.  
  18. # overwrites init method to grab the user from the kwargs and assign it to the ran_by, also it hides the field as this operation is done automatically prior to save the form to do the name validation
  19. def __init__(self, *args, **kwargs):
  20. user = kwargs.pop("user", None)
  21. super().__init__(*args, **kwargs)
  22. if user:
  23. self.fields["model"].choices = [
  24. ("regional_model.pkl", "Regional (Ozama)"),
  25. ("national_model.pkl", "Dominican Republic"),
  26. ]
  27. self.fields["ran_by"].initial = user
  28. self.fields["ran_by"].widget = forms.HiddenInput()
  29.  
  30.  
  31. class ShockForm(forms.Form):
  32. BASE_CHOICES = [
  33. ("Administracion", "Administracion"),
  34. ("Agropecuario", "Agropecuario"),
  35. ("Comercio", "Comercio"),
  36. ("Comunicaciones", "Comunicaciones"),
  37. ("Construccion", "Construccion"),
  38. ("Educacion", "Educacion"),
  39. ("Energia", "Energia"),
  40. ("Extraccion", "Extraccion"),
  41. ("Finanzas", "Finanzas"),
  42. ("Hosteleria", "Hosteleria"),
  43. ("Inmobiliaria", "Inmobiliaria"),
  44. ("Logistica", "Logistica"),
  45. ("Manufactura", "Manufactura"),
  46. ("Otros", "Otros"),
  47. ("Salud", "Salud"),
  48. ]
  49.  
  50. sector = forms.ChoiceField(
  51. choices=BASE_CHOICES,
  52. required=True,
  53. )
  54. amount = forms.FloatField(
  55. required=True,
  56. validators=[MinValueValidator(0.001)],
  57. )
  58. impact_area = forms.ChoiceField(
  59. choices=[
  60. ("rest_of_country", "Rest of the country"),
  61. ("region_of_interest", "Model's region"),
  62. ],
  63. required=False,
  64. )
  65.  
  66. def __init__(self, *args, **kwargs):
  67. simulation_model = kwargs.pop("model", None)
  68. super().__init__(*args, **kwargs)
  69. if simulation_model == "national_model.pkl":
  70. self.fields["impact_area"].initial = None
  71. else:
  72. self.fields["impact_area"].choices = [
  73. ("rest_of_country", "Rest of the country"),
  74. ("region_of_interest", "Model's region"),
  75. ]
  76.  
  77.  
  78. ShockFormSet = forms.formset_factory(ShockForm, extra=0, min_num=1, validate_min=True, max_num=15, validate_max=True)
  79.  
  80.  
  81.  
  82.  
  83. view.py
  84. def create_simulation_view(request):
  85. # Handle POST request
  86. if request.method == "POST":
  87. # Initialize forms with POST data
  88. form = SimulationForm(request.POST, user=request.user)
  89. shock_formset = ShockFormSet(request.POST, form_kwargs={"model": request.POST.get("model")})
  90. # Validate forms
  91. if form.is_valid() and shock_formset.is_valid():
  92. # Save the main simulation form
  93. simulation = form.save()
  94. # Process shock formset data
  95. shocks_data = {}
  96. for shock_form in shock_formset:
  97. if shock_form.cleaned_data:
  98. sector = shock_form.cleaned_data["sector"]
  99. impact_area = shock_form.cleaned_data["impact_area"]
  100. # Process sector based on impact area
  101. if impact_area == "region_of_interest":
  102. sector_cleaned = f"{sector}_r"
  103. elif impact_area == "rest_of_country":
  104. sector_cleaned = f"{sector}_s"
  105. else:
  106. sector_cleaned = sector
  107. amount = shock_form.cleaned_data["amount"]
  108. shocks_data[sector_cleaned] = amount
  109. # Save shocks data to simulation
  110. simulation.shocks = shocks_data
  111. simulation.save()
  112. messages.success(request, "Simulation created successfully!")
  113. return redirect("simulation_details", pk=simulation.id)
  114. # If forms are invalid
  115. messages.error(request, "There are errors on the form, please review the data entered")
  116. # This list of errors won't be displayed on the site as errors are handled by Django already. The code remains for debugging purposes as is the way to collect the error message from Django structure
  117. error_list = []
  118. if form.errors:
  119. error_list.append(f"Simulation form errors: {form.errors.as_text()}")
  120. if shock_formset.errors:
  121. for i, shock_form in enumerate(shock_formset):
  122. if shock_form.errors:
  123. error_list.append(f"Shock form {i} errors: {shock_form.errors.as_text()}")
  124.  
  125. # Handle GET request
  126. else:
  127. form = SimulationForm(user=request.user)
  128. shock_formset = ShockFormSet(form_kwargs={"model": "regional_model.pkl"})
  129.  
  130. model = request.POST.get("model", "regional_model.pkl")
  131. is_regional = model != "national_model.pkl"
  132.  
  133. context = {"form": form, "shock_formset": shock_formset, "is_regional": is_regional}
  134.  
  135. return render(request, "simulation_shock_form.html", context)
  136.  
  137.  
  138.  
  139.  
  140.  
  141.  
  142. Template
  143. {% extends 'core.html' %}
  144. {% load static %}
  145.  
  146. {% block content %}
  147. <h1>{% if is_edit %}Edit{% else %}Prepare{% endif %} the simulation</h1>
  148. <form method="post" id="combined-simulation-form">
  149. {% csrf_token %}
  150. <div class="simulation-details">
  151. {{ form.as_p }}
  152. </div>
  153. <h3>How much each sector will receive?</h3>
  154. <p>Select the disbursement for every sector:</p>
  155. <div class="shocks-section">
  156. {{ shock_formset.management_form }}
  157. <div id="shock-forms-container">
  158. {% for form in shock_formset %}
  159. <div class="shock-form" data-form-index="{{ forloop.counter0 }}">
  160. <h4 class="sector-header">Sector {{ forloop.counter }}</h4>
  161.  
  162. {% for hidden in form.hidden_fields %}
  163. {{ hidden }}
  164. {% endfor %}
  165.  
  166. <div class="form-group">
  167. {{ form.sector.label_tag }}
  168. <select name="{{ form.sector.html_name }}"
  169. id="{{ form.sector.auto_id }}"
  170. class="sector-select"
  171. data-initial-sector="{{ form.initial.sector|default:'' }}">
  172. {% for value, label in form.fields.sector.choices %}
  173. <option value="{{ value }}"
  174. {% if form.initial.sector == value %}selected{% endif %}>
  175. {{ label }}
  176. </option>
  177. {% endfor %}
  178. </select>
  179. {% if form.sector.errors %}
  180. <div class="error">{{ form.sector.errors }}</div>
  181. {% endif %}
  182. </div>
  183.  
  184. <div class="form-group">
  185. {{ form.amount.label_tag }}
  186. <input type="number"
  187. name="{{ form.amount.html_name }}"
  188. id="{{ form.amount.auto_id }}"
  189. value="{{ form.initial.amount|default_if_none:'' }}"
  190. required
  191. min="0.001"
  192. step="any"
  193. class="amount-input"/> (millions USD)
  194. {% if form.amount.errors %}
  195. <div class="error">{{ form.amount.errors }}</div>
  196. {% endif %}
  197. </div>
  198.  
  199. <div class="form-group impact-area-group" style="display: {% if not is_regional %}none{% endif %};">
  200. {{ form.impact_area.label_tag }}
  201. <select name="{{ form.impact_area.html_name }}"
  202. id="{{ form.impact_area.auto_id }}"
  203. class="impact-area-select"
  204. data-initial-area="{{ form.initial.impact_area|default:'' }}">
  205. {% for value, label in form.fields.impact_area.choices %}
  206. <option value="{{ value }}"
  207. {% if form.initial.impact_area == value %}selected{% endif %}>
  208. {{ label }}
  209. </option>
  210. {% endfor %}
  211. </select>
  212. {% if form.impact_area.errors %}
  213. <div class="error">{{ form.impact_area.errors }}</div>
  214. {% endif %}
  215. </div>
  216.  
  217. {% if not forloop.first %}
  218. <button type="button" class="delete-form-btn">Delete</button>
  219. {% endif %}
  220. </div>
  221. {% endfor %}
  222. </div>
  223. <br>
  224. <button type="button" id="add-form-btn">Add another sector</button>
  225. </div>
  226. <hr>
  227. <button type="submit">{% if is_edit %}Update{% else %}Save{% endif %} simulation</button>
  228. </form>
  229.  
  230. <script src="{% static 'scripts/sector_unique_choices.js' %}"></script>
  231. {% endblock %}
  232.  
  233.  
  234.  
  235.  
  236.  
  237. JavaScript
  238. document.addEventListener("DOMContentLoaded", function () {
  239. const formsContainer = document.getElementById("shock-forms-container");
  240. const addButton = document.getElementById("add-form-btn");
  241. const modelSelect = document.querySelector("#id_model");
  242. let totalForms = document.querySelector('[name="form-TOTAL_FORMS"]'); // selects the hidden input field that Django's formset creates to track the total number of forms
  243.  
  244. // Function to handle model selection change
  245. function handleModelChange() {
  246. const isNationalModel = modelSelect.value === "national_model.pkl";
  247. const impactAreaGroups = document.querySelectorAll(".impact-area-group"); // selects the HTML div elements
  248. const impactAreaSelects = document.querySelectorAll(".impact-area-select"); // selects the HTML selects elements
  249. impactAreaGroups.forEach((group, index) => {
  250. group.style.display = isNationalModel ? "none" : "block";
  251. let initialValue = impactAreaSelects[index].getAttribute('data-initial-area');
  252. if (isNationalModel) {
  253. impactAreaSelects[index].value = null;
  254. } else {
  255. if (initialValue === null) {
  256. initialValue = 'rest_of_country'
  257. }
  258. impactAreaSelects[index].value = initialValue || "rest_of_country";
  259. }
  260. });
  261. }
  262.  
  263. // Function to find first available sector that isn't already selected
  264. function getFirstAvailableSector(currentSelect) {
  265. const sectorSelects = document.querySelectorAll(".sector-select"); // take all select elements
  266. const selectedSectors = Array.from(sectorSelects).map((select) => select.value); // save the choices for those select elements
  267. const selectElement = document.querySelector(".sector-select"); // take the first select element (can be any) and and extract all the options
  268. const allSectors = Array.from(selectElement.options).map((option) => option.value); // save all the options from that select (15 sectors)
  269. if (currentSelect && currentSelect.value) {
  270. return currentSelect.value;
  271. } // if the select element has a selection, keep it
  272. return allSectors.find((sector) => !selectedSectors.includes(sector)); // find first sector that isn't in selectedSectors
  273. }
  274.  
  275. // Function to keep track of used sectors to disable the dropdown
  276. function updateSectorChoices() {
  277. const sectorSelects = document.querySelectorAll(".sector-select"); // take all select elements from HTML
  278. const selectedSectors = Array.from(sectorSelects).map((select) => select.value); // save the choices for those select elements
  279. // disables the option of each select element if it is on the selectedSectors variable (and is not the current selection)
  280. sectorSelects.forEach((select) => {
  281. const currentValue = select.value;
  282. Array.from(select.options).forEach((option) => {
  283. option.disabled =
  284. selectedSectors.includes(option.value) &&
  285. option.value !== currentValue; // do not disable current value
  286. });
  287. });
  288. }
  289.  
  290.  
  291. // Event listener to handle new form addition by clicking on the button
  292. addButton.addEventListener("click", function () {
  293. const forms = document.querySelectorAll(".shock-form"); // selects teh shock form div element from HTML
  294. if (forms.length >= 15) {
  295. alert("Maximum number of sectors reached");
  296. return;
  297. }
  298.  
  299. // 1. Clone the first form
  300. const newForm = forms[0].cloneNode(true);
  301. const formIndex = forms.length;
  302.  
  303. // 2. Update form index and header label with its number
  304. newForm.setAttribute("data-form-index", formIndex);
  305. newForm.querySelector(".sector-header").textContent = `Sector ${formIndex + 1}`;
  306.  
  307. // 3. Update form elements id and selection field
  308. newForm.querySelectorAll("input, select").forEach((element) => {
  309. element.name = element.name.replace("-0-", `-${formIndex}-`);
  310. element.id = element.id.replace("-0-", `-${formIndex}-`);
  311. if (element.type !== "hidden") {
  312. if (element.classList.contains("sector-select")) {
  313. const availableSector = getFirstAvailableSector(null);
  314. if (availableSector) {
  315. element.value = availableSector; // set new select element to first available sector
  316. }
  317. } else if (element.tagName === "SELECT") {
  318. element.selectedIndex = 0; // for other selects (like impact area), display the first option by default
  319. } else {
  320. element.value = ""; // for other inputs (like amount), clear the value
  321. }
  322. }
  323. });
  324.  
  325. // 4. Add delete button
  326. const deleteBtn = document.createElement("button");
  327. deleteBtn.type = "button";
  328. deleteBtn.className = "delete-form-btn";
  329. deleteBtn.textContent = "Delete";
  330. newForm.appendChild(deleteBtn);
  331.  
  332. // 5. Update form count for the Django formset
  333. formsContainer.appendChild(newForm);
  334. totalForms.value = forms.length + 1;
  335.  
  336. // 6. Call update sector choices to update the options of every select
  337. updateSectorChoices();
  338. });
  339.  
  340.  
  341. // Event listener to handle form deletion
  342. formsContainer.addEventListener("click", function (e) {
  343. if (e.target.classList.contains("delete-form-btn")) {
  344. const formDiv = e.target.closest(".shock-form"); // associate the button with the closest form and remove it
  345. formDiv.remove();
  346. const forms = document.querySelectorAll(".shock-form");
  347. totalForms.value = forms.length; // update form count and indices
  348. // renumber remaining forms
  349. forms.forEach((form, index) => {
  350. form.setAttribute("data-form-index", index);
  351. form.querySelector(".sector-header").textContent = `Sector ${index + 1}`;
  352. form.querySelectorAll("input, select").forEach((element) => {
  353. element.name = element.name.replace(/-\d+-/, `-${index}-`);
  354. element.id = element.id.replace(/-\d+-/, `-${index}-`);
  355. });
  356. });
  357. updateSectorChoices();
  358. }
  359. });
  360.  
  361.  
  362. // Event listeners to handle sector selection and model changes
  363. formsContainer.addEventListener("change", function (e) {
  364. if (e.target.name.endsWith("-sector")) {
  365. updateSectorChoices();
  366. }
  367. });
  368. modelSelect.addEventListener("change", handleModelChange);
  369.  
  370. // Initialize sector choices
  371. updateSectorChoices();
  372. });
Advertisement
Add Comment
Please, Sign In to add comment