Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Relevant code for the forum question
- forms.py
- class SimulationForm(forms.ModelForm):
- class Meta:
- model = Simulation
- fields = ["model", "name", "description", "ran_by"]
- widgets = {
- "name": forms.TextInput(attrs={"size": 40, "placeholder": "Input a reference name"}),
- "description": forms.Textarea(
- attrs={"placeholder": "Give some context to the simulation you are about to create here"}
- ),
- }
- # 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
- def __init__(self, *args, **kwargs):
- user = kwargs.pop("user", None)
- super().__init__(*args, **kwargs)
- if user:
- self.fields["model"].choices = [
- ("regional_model.pkl", "Regional (Ozama)"),
- ("national_model.pkl", "Dominican Republic"),
- ]
- self.fields["ran_by"].initial = user
- self.fields["ran_by"].widget = forms.HiddenInput()
- class ShockForm(forms.Form):
- BASE_CHOICES = [
- ("Administracion", "Administracion"),
- ("Agropecuario", "Agropecuario"),
- ("Comercio", "Comercio"),
- ("Comunicaciones", "Comunicaciones"),
- ("Construccion", "Construccion"),
- ("Educacion", "Educacion"),
- ("Energia", "Energia"),
- ("Extraccion", "Extraccion"),
- ("Finanzas", "Finanzas"),
- ("Hosteleria", "Hosteleria"),
- ("Inmobiliaria", "Inmobiliaria"),
- ("Logistica", "Logistica"),
- ("Manufactura", "Manufactura"),
- ("Otros", "Otros"),
- ("Salud", "Salud"),
- ]
- sector = forms.ChoiceField(
- choices=BASE_CHOICES,
- required=True,
- )
- amount = forms.FloatField(
- required=True,
- validators=[MinValueValidator(0.001)],
- )
- impact_area = forms.ChoiceField(
- choices=[
- ("rest_of_country", "Rest of the country"),
- ("region_of_interest", "Model's region"),
- ],
- required=False,
- )
- def __init__(self, *args, **kwargs):
- simulation_model = kwargs.pop("model", None)
- super().__init__(*args, **kwargs)
- if simulation_model == "national_model.pkl":
- self.fields["impact_area"].initial = None
- else:
- self.fields["impact_area"].choices = [
- ("rest_of_country", "Rest of the country"),
- ("region_of_interest", "Model's region"),
- ]
- ShockFormSet = forms.formset_factory(ShockForm, extra=0, min_num=1, validate_min=True, max_num=15, validate_max=True)
- view.py
- def create_simulation_view(request):
- # Handle POST request
- if request.method == "POST":
- # Initialize forms with POST data
- form = SimulationForm(request.POST, user=request.user)
- shock_formset = ShockFormSet(request.POST, form_kwargs={"model": request.POST.get("model")})
- # Validate forms
- if form.is_valid() and shock_formset.is_valid():
- # Save the main simulation form
- simulation = form.save()
- # Process shock formset data
- shocks_data = {}
- for shock_form in shock_formset:
- if shock_form.cleaned_data:
- sector = shock_form.cleaned_data["sector"]
- impact_area = shock_form.cleaned_data["impact_area"]
- # Process sector based on impact area
- if impact_area == "region_of_interest":
- sector_cleaned = f"{sector}_r"
- elif impact_area == "rest_of_country":
- sector_cleaned = f"{sector}_s"
- else:
- sector_cleaned = sector
- amount = shock_form.cleaned_data["amount"]
- shocks_data[sector_cleaned] = amount
- # Save shocks data to simulation
- simulation.shocks = shocks_data
- simulation.save()
- messages.success(request, "Simulation created successfully!")
- return redirect("simulation_details", pk=simulation.id)
- # If forms are invalid
- messages.error(request, "There are errors on the form, please review the data entered")
- # 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
- error_list = []
- if form.errors:
- error_list.append(f"Simulation form errors: {form.errors.as_text()}")
- if shock_formset.errors:
- for i, shock_form in enumerate(shock_formset):
- if shock_form.errors:
- error_list.append(f"Shock form {i} errors: {shock_form.errors.as_text()}")
- # Handle GET request
- else:
- form = SimulationForm(user=request.user)
- shock_formset = ShockFormSet(form_kwargs={"model": "regional_model.pkl"})
- model = request.POST.get("model", "regional_model.pkl")
- is_regional = model != "national_model.pkl"
- context = {"form": form, "shock_formset": shock_formset, "is_regional": is_regional}
- return render(request, "simulation_shock_form.html", context)
- Template
- {% extends 'core.html' %}
- {% load static %}
- {% block content %}
- <h1>{% if is_edit %}Edit{% else %}Prepare{% endif %} the simulation</h1>
- <form method="post" id="combined-simulation-form">
- {% csrf_token %}
- <div class="simulation-details">
- {{ form.as_p }}
- </div>
- <h3>How much each sector will receive?</h3>
- <p>Select the disbursement for every sector:</p>
- <div class="shocks-section">
- {{ shock_formset.management_form }}
- <div id="shock-forms-container">
- {% for form in shock_formset %}
- <div class="shock-form" data-form-index="{{ forloop.counter0 }}">
- <h4 class="sector-header">Sector {{ forloop.counter }}</h4>
- {% for hidden in form.hidden_fields %}
- {{ hidden }}
- {% endfor %}
- <div class="form-group">
- {{ form.sector.label_tag }}
- <select name="{{ form.sector.html_name }}"
- id="{{ form.sector.auto_id }}"
- class="sector-select"
- data-initial-sector="{{ form.initial.sector|default:'' }}">
- {% for value, label in form.fields.sector.choices %}
- <option value="{{ value }}"
- {% if form.initial.sector == value %}selected{% endif %}>
- {{ label }}
- </option>
- {% endfor %}
- </select>
- {% if form.sector.errors %}
- <div class="error">{{ form.sector.errors }}</div>
- {% endif %}
- </div>
- <div class="form-group">
- {{ form.amount.label_tag }}
- <input type="number"
- name="{{ form.amount.html_name }}"
- id="{{ form.amount.auto_id }}"
- value="{{ form.initial.amount|default_if_none:'' }}"
- required
- min="0.001"
- step="any"
- class="amount-input"/> (millions USD)
- {% if form.amount.errors %}
- <div class="error">{{ form.amount.errors }}</div>
- {% endif %}
- </div>
- <div class="form-group impact-area-group" style="display: {% if not is_regional %}none{% endif %};">
- {{ form.impact_area.label_tag }}
- <select name="{{ form.impact_area.html_name }}"
- id="{{ form.impact_area.auto_id }}"
- class="impact-area-select"
- data-initial-area="{{ form.initial.impact_area|default:'' }}">
- {% for value, label in form.fields.impact_area.choices %}
- <option value="{{ value }}"
- {% if form.initial.impact_area == value %}selected{% endif %}>
- {{ label }}
- </option>
- {% endfor %}
- </select>
- {% if form.impact_area.errors %}
- <div class="error">{{ form.impact_area.errors }}</div>
- {% endif %}
- </div>
- {% if not forloop.first %}
- <button type="button" class="delete-form-btn">Delete</button>
- {% endif %}
- </div>
- {% endfor %}
- </div>
- <br>
- <button type="button" id="add-form-btn">Add another sector</button>
- </div>
- <hr>
- <button type="submit">{% if is_edit %}Update{% else %}Save{% endif %} simulation</button>
- </form>
- <script src="{% static 'scripts/sector_unique_choices.js' %}"></script>
- {% endblock %}
- JavaScript
- document.addEventListener("DOMContentLoaded", function () {
- const formsContainer = document.getElementById("shock-forms-container");
- const addButton = document.getElementById("add-form-btn");
- const modelSelect = document.querySelector("#id_model");
- 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
- // Function to handle model selection change
- function handleModelChange() {
- const isNationalModel = modelSelect.value === "national_model.pkl";
- const impactAreaGroups = document.querySelectorAll(".impact-area-group"); // selects the HTML div elements
- const impactAreaSelects = document.querySelectorAll(".impact-area-select"); // selects the HTML selects elements
- impactAreaGroups.forEach((group, index) => {
- group.style.display = isNationalModel ? "none" : "block";
- let initialValue = impactAreaSelects[index].getAttribute('data-initial-area');
- if (isNationalModel) {
- impactAreaSelects[index].value = null;
- } else {
- if (initialValue === null) {
- initialValue = 'rest_of_country'
- }
- impactAreaSelects[index].value = initialValue || "rest_of_country";
- }
- });
- }
- // Function to find first available sector that isn't already selected
- function getFirstAvailableSector(currentSelect) {
- const sectorSelects = document.querySelectorAll(".sector-select"); // take all select elements
- const selectedSectors = Array.from(sectorSelects).map((select) => select.value); // save the choices for those select elements
- const selectElement = document.querySelector(".sector-select"); // take the first select element (can be any) and and extract all the options
- const allSectors = Array.from(selectElement.options).map((option) => option.value); // save all the options from that select (15 sectors)
- if (currentSelect && currentSelect.value) {
- return currentSelect.value;
- } // if the select element has a selection, keep it
- return allSectors.find((sector) => !selectedSectors.includes(sector)); // find first sector that isn't in selectedSectors
- }
- // Function to keep track of used sectors to disable the dropdown
- function updateSectorChoices() {
- const sectorSelects = document.querySelectorAll(".sector-select"); // take all select elements from HTML
- const selectedSectors = Array.from(sectorSelects).map((select) => select.value); // save the choices for those select elements
- // disables the option of each select element if it is on the selectedSectors variable (and is not the current selection)
- sectorSelects.forEach((select) => {
- const currentValue = select.value;
- Array.from(select.options).forEach((option) => {
- option.disabled =
- selectedSectors.includes(option.value) &&
- option.value !== currentValue; // do not disable current value
- });
- });
- }
- // Event listener to handle new form addition by clicking on the button
- addButton.addEventListener("click", function () {
- const forms = document.querySelectorAll(".shock-form"); // selects teh shock form div element from HTML
- if (forms.length >= 15) {
- alert("Maximum number of sectors reached");
- return;
- }
- // 1. Clone the first form
- const newForm = forms[0].cloneNode(true);
- const formIndex = forms.length;
- // 2. Update form index and header label with its number
- newForm.setAttribute("data-form-index", formIndex);
- newForm.querySelector(".sector-header").textContent = `Sector ${formIndex + 1}`;
- // 3. Update form elements id and selection field
- newForm.querySelectorAll("input, select").forEach((element) => {
- element.name = element.name.replace("-0-", `-${formIndex}-`);
- element.id = element.id.replace("-0-", `-${formIndex}-`);
- if (element.type !== "hidden") {
- if (element.classList.contains("sector-select")) {
- const availableSector = getFirstAvailableSector(null);
- if (availableSector) {
- element.value = availableSector; // set new select element to first available sector
- }
- } else if (element.tagName === "SELECT") {
- element.selectedIndex = 0; // for other selects (like impact area), display the first option by default
- } else {
- element.value = ""; // for other inputs (like amount), clear the value
- }
- }
- });
- // 4. Add delete button
- const deleteBtn = document.createElement("button");
- deleteBtn.type = "button";
- deleteBtn.className = "delete-form-btn";
- deleteBtn.textContent = "Delete";
- newForm.appendChild(deleteBtn);
- // 5. Update form count for the Django formset
- formsContainer.appendChild(newForm);
- totalForms.value = forms.length + 1;
- // 6. Call update sector choices to update the options of every select
- updateSectorChoices();
- });
- // Event listener to handle form deletion
- formsContainer.addEventListener("click", function (e) {
- if (e.target.classList.contains("delete-form-btn")) {
- const formDiv = e.target.closest(".shock-form"); // associate the button with the closest form and remove it
- formDiv.remove();
- const forms = document.querySelectorAll(".shock-form");
- totalForms.value = forms.length; // update form count and indices
- // renumber remaining forms
- forms.forEach((form, index) => {
- form.setAttribute("data-form-index", index);
- form.querySelector(".sector-header").textContent = `Sector ${index + 1}`;
- form.querySelectorAll("input, select").forEach((element) => {
- element.name = element.name.replace(/-\d+-/, `-${index}-`);
- element.id = element.id.replace(/-\d+-/, `-${index}-`);
- });
- });
- updateSectorChoices();
- }
- });
- // Event listeners to handle sector selection and model changes
- formsContainer.addEventListener("change", function (e) {
- if (e.target.name.endsWith("-sector")) {
- updateSectorChoices();
- }
- });
- modelSelect.addEventListener("change", handleModelChange);
- // Initialize sector choices
- updateSectorChoices();
- });
Advertisement
Add Comment
Please, Sign In to add comment