Check if record exists in model

Actually, you pretty much nailed it.

It’s a reference to a row in another table.

Normally, you refer to rows via their primary key. So the FK in one table is an int that matches the PK of the table to which it refers.

The to_name clause changes that. Instead of referring to the PK, it can refer to any field that is unique in the table. In a majority of cases, this tends to be a bad idea. (There are a number of cases where it’s appropriate to do this, but they tend to be few and far between.)

However, the key point here is that you’ve got your FK referring to a non-PK field. This non-PK field is a different data type than the PK.

This brings us back to an earlier response (Check if record exists in model - #26 by KenWhitesell)

My primary recommendation is to eliminate the to_field attribute of that FK specification.

Now, since this is going to change the data type of the underlying column, you will need to do a makemigrations / migrate to make the database match your model.

Hopefully, you don’t have any data in that table that you’re concerned about.

If you have any existing data in that table that you want to keep, the situation has now become a lot more complicated. Let me know if that’s the case, and I’ll give you an outline of the steps you need to take to actually accomplish this.

Also note there’s still at least one more problem needing to be addressed - this is not the last item to fix.

Well i gathered that was the problem, so i removed the to_field='project_name' from the model and recreated the database.

But it didn;t allow me to POST to the database, so i put the to_field='project_name' back and it allowed me to POST again.

Cross-posted timing again - please see prior response.

Ok, so problem 1 - i need to remove this to_field='project_name' and then recreate the database.
Whats this 2nd problem :frowning:

Sorry Ken, i bet you wish you never replied.

The next known problem is in this block of code:

This is not how you add external data to an object being created from a form.

See the second example at this section of the docs .

The general flow is to save the form with commit=False, keeping the reference to the object being created. Then update that object and save the changed object.

There might be an issue with your form - but that’s only conjecture based upon what you’ve posted so far. If I’m misreading the tea leaves, then it’s not a problem.

Not at all - this is why I hang around here.

So what i dont get about the example on the link is this:
new_author = f.save(commit=False) what is new_author is that just a variable?
And then when new_author.some_field = 'some_value' is that just adding some_value to the some_field but where did the some_field come from? Does that already exist in the AuthorForm Model?

When i look at the AuthorFrom Model i only see Name, Title and birth_date availble

And then if that is correct - how does new_author relate to the data being fed in by the form POST?

To be honest i find the Django doc’s a bit hard to follow as a beginner.

Yes, new_author is a new variable to hold the reference to the newly-created object.

It is assigning the string some_value to the attribute some_field of the new_author object.

No it does not, nor does it technically need to.

It’s really intended as a generic placeholder. It could be a new attribute being created, or it’s a stand-in for any of the existing fields

While in the grand scheme of things that statement is meaningless, it is not an invalid statement. You can arbitrarily add attributes to objects at will. There is no requirement for those attributes to previously exist in the object. (However, it is meaningless in that since that field is not defined in the model, it won’t be saved to the database.)

You raise a good point - that’s an area that could probably be improved in the docs.
(Note: Author is the Model, AuthorForm is a Form, but I understand what you’re getting at.)

Quoting the relevant example:

# Create a form instance with POST data.
>>> f = AuthorForm(request.POST)

# Create, but don't save the new author instance.
>>> new_author = f.save(commit=False)

# Modify the author in some way.
>>> new_author.some_field = 'some_value'

# Save the new instance.
>>> new_author.save()

The variable new_author is created from the output of f.save(....

The variable f is created from the line f = AuthorForm(request.POST)

There is an assumption that the person learning Django has some knowledge and experience with Python. If not, that individual is faced with the task of climbing two learning curves at the same time. (Since Django is Python code, there’s not really any way around that, either. If you’re working with Django, you are going to be reading and writing Python code.)

I actually wanted to learn Python and i thought building a app with Django would be a good way to keep me interested. I understand its going to be tough. Lucky there are people like you to guide :slight_smile:

So back to that view then, i realise that i need to change this now, but i stuggle with the syntax.

def add_fundamentals_view(request, project_id):
    project = get_object_or_404(Project, pk=project_id)
    if request.method == 'POST':
        form = AddFundamentalsForm(request.POST)
        if form.is_valid():
            form.instance.project = project
            form.save()
            return redirect('dashboard.html')
    else:
        form = AddFundamentalsForm()
    return render(request, 'pages/add_fundamentals.html', {'project': project, "form": form})

Trying to follow the example my view would be:

def add_fundamentals_view(request, project_id):
    project = get_object_or_404(Project, pk=project_id)
    if request.method == 'POST':
        form = AddFundamentalsForm(request.POST)
        fundamental = form.save(commit=False)
        fundamental = (request.POST) # This being the request body?
        if form.is_valid():
            form.instance.project = project
            fundamental.save()
            return redirect('dashboard.html')
    else:
        form = AddFundamentalsForm()
    return render(request, 'pages/add_fundamentals.html', {'project': project, "form": form})

Opinion - it’s less the syntax - you’re creating valid Python code. It’s more an issue of semantics. It’s that understanding of what each statement is doing, and the meaning and significance of the objects being used where you should be focusing your efforts.

Really close.
Note the comments.

    if request.method == 'POST':
        # Create the form object from the submitted data
        form = AddFundamentalsForm(request.POST)
        if form.is_valid():
            # Create the instance of fundamental from the form
            fundamental = form.save(commit=False)
            # Modify the fundamental object to add the reference to the project
            fundamental.project = project
            # Save the modified fundamental object
            fundamental.save()
            return redirect('dashboard.html')

Ive made the change to the model and updated database, updated the view to

def add_fundamentals_view(request, project_id):
    project = get_object_or_404(Project, pk=project_id)
    if request.method == 'POST':
        form = AddFundamentalsForm(request.POST)
        if form.is_valid():
            fundamental = form.save(commit=False)
            fundamental.project = project
            fundamental.save()
            return redirect('dashboard.html')
    else:
        form = AddFundamentalsForm()
    return render(request, 'pages/add_fundamentals.html', {'project': project, "form": form})

but it still wont update the database. Not getting an error.

I get 200 in terminal "POST /apps/fundamentals/5 HTTP/1.1" 200 80274

So now it’s time to examine the form and template. Please post the AddFundamentalsForm and your add_fundamentals.html template.

Also, need clarification here - I know you’ve made some changes to the Fundamentals Model definition. What is the current name of the field that is the ForeignKey field to Project? Is it now project? Or is it still project_name? If the latter, then the assignment to fundamentals.project should be to fundamentals.project_name

Thanks Ken.

My form:

class AddFundamentalsForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(AddFundamentalsForm, self).__init__(*args, **kwargs)

    class Meta:
        model = Fundamentals
        fields = '__all__'

The models field name is project_name and i have updated the view to:

def add_fundamentals_view(request, project_id):

    project = get_object_or_404(Project, pk=project_id)

    if request.method == 'POST':

        form = AddFundamentalsForm(request.POST)

        if form.is_valid():

           fundamental = form.save(commit=False)

           fundamental.project_name = project

           fundamental.save()

           return redirect('dashboard.html')

    else:

        form = AddFundamentalsForm()

    return render(request, 'pages/add_fundamentals.html', {'project': project, "form": form})

Model field

project_name = models.ForeignKey(Project, on_delete=models.CASCADE)

After making this last change, is it still not saving to the database?

When you submit, is it redisplaying the form, or is it taking you to the dashboard page?

I see in your form Meta class that you’ve got fields = '__all__'. This should be rendering the project_name as a drop-down. Are you seeing that in your form? (Is that what you want to see?)

What does your template look like?

When i submit it reloads the form page and doesn’t redirect.

<h4 class="card-title mb-0">Critical Fundamentals for {{ project.project_name }} </h4> is returning the project name

I am going to tidy up my template cause there are so many form files its hard to see. So i will remove all and just leave a couple and share.

As its not redirecting would that imply that the form isn’t valid?

That’s correct - one of the things I’ve wanted to see in the template is whether or not you’re rendering any form errors.
If you’re not, then that’s the next step. (See Rendering fields manually)

That’s not part of the form.

If you’re rendering individual fields, then you must limit your form to those fields being rendered.
Any required model fields that are part of the form but not rendered / submitted is going to cause the form to be invalid.

Im not rendering any errors. But the form does work when i add the to_field line in the model. So that would suggest the form is ok?

i will post back the cut down form in a bit, but the only requried field set by the model is project_name that is getting pre-populated by
{{ project.project_name }}

No it is not suggesting that. That’s more coincidence than anything else. (Besides, if it was all working with that clause in your model, we wouldn’t still be here trying to get this fixed.)

By not rendering the errors, you’re preventing yourself from seeing what the real problem is.

That is rendering text on a page, that is not setting any form field value. (Admittedly, that’s still part conjecture because you have not yet posted your template.)

Hey Ken,

Broken this down now to just 3 fields, one being the project_name

<form class="needs-validation" id="add-data" novalidate method="post" enctype="multipart/form-data"
onsubmit="return false;">
    {% csrf_token %}
    <div class="row">
        <div class="col-12">
            <div class="card">
                <div class="card-body">
                    <div class="col-sm">
                        <div class="mb-3">
                            <label for="project-name-label">Project Name</label>
                            <input type="text" class="form-control" id="project-name" value="{{ project.project_name }}" required readonly>
                        </div>
                    </div>
                    <div class="row">
                        <div class="col-sm">
                            <div class="accordion" id="accordionExample">
                                <div class="accordion-item">
                                  <h2 class="accordion-header" id="headingOne">
                                    <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
                                        Does the project land in our Tiered entry levels?
                                    </button>
                                  </h2>
                                  <div id="collapseOne" class="accordion-collapse collapse show" aria-labelledby="headingOne" data-bs-parent="#accordionExample">
                                    <div class="accordion-body">
                                        <div class="col-sm">
                                            <div class="mb-3">
                                                <select class="form-control" name="choices-single-no-sorting" id="project-tier-entry-level">
                                                    <option value="" disabled selected hidden>Please Choose...</option>  
                                                    <option>Yes</option>
                                                    <option>No</option>
                                                    <option>Unknown</option>
                                                </select>
                                            </div>
                                            <div class="row">
                                                <div class="col-sm">
                                                    <div class="mb-3">
                                                        <textarea id="project-tier-entry-level-notes" class="form-control" rows="3" placeholder="Add comments here"></textarea>
                                                    </div>
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                  </div>
                                </div>

                              </div>
                        </div>
                    </div>
                </br>                        
                    <div class="col-auto">
                        <button class="btn btn-primary" type="submit">Submit form</button>
                    </div>
                </div>
            </div>
        </div> <!-- end col -->
    </div>
</form>

Still doesn’t POST.

JavaScript used to validate and POST

var submitProjectData = document.getElementById('add-data');
submitProjectData.addEventListener('submit', function(e) {
    e.preventDefault();
    
    if (validated(this)) {
        this.classList.add("was-validated");

        var p_projectname = document.getElementById("project-name").value;     
        var p_projecttierentrylevel = document.getElementById("project-tier-entry-level").value;
        var p_projecttierentrylevelnotes = document.getElementById("project-tier-entry-level-notes").value;


      
        const csrftoken = document.getElementsByName("csrfmiddlewaretoken")[0].value;

        const formdata = new FormData();
        formdata.append("project_name", p_projectname );
        formdata.append("project_tier_entry_level", p_projecttierentrylevel );
        formdata.append("project_tier_entry_level_notes", p_projecttierentrylevelnotes );

        const xhr = new XMLHttpRequest();
        xhr.open("POST", window.location.href);
        xhr.setRequestHeader("X-CSRFToken", csrftoken);
        xhr.send(formdata);
        xhr.onload = () => {
            window.location.reload();
        };
    }
})


// Form validation 
function validated(form) {
    console.log(form)

    var p_name = document.getElementById("project-name");
    p_name_value = p_name.value.trim();



    if (p_name_value === "") {
        message = "Please fill investor name field"
        setErrorFor(p_name, message);
    } else {
        setSuccessFor(p_name)
        return true;
    }
}


//  For Display Eroor
function setErrorFor(element, message) {
    element.classList.remove('is-valid');
    element.classList.add('is-invalid');
    parent = element.parentNode;
    for (var i = 0; i < parent.childNodes.length; i++) {
        if (parent.childNodes[i].className == "invalid-feedback") {
            notes = parent.childNodes[i];
            notes.innerHTML = message;
            break;
        }
    }
}

// For success Message
function setSuccessFor(element) {
    element.classList.remove('is-invalid');
    element.classList.add('is-valid');
}

// Check URL Pattern 
function isUrl(url) {
    var re = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
    return re.test(url)
}

Ok, this is clearing up the picture quite a bit.

<input type="text" class="form-control" id="project-name" value="{{ project.project_name }}" required readonly>

There’s a fundamental issue with having this as a field in your form. The fact that it’s there as a form field is going to cause problems. Even though you’ve got it marked as readonly, the problems still exist. (Someone could use their browser’s developer tools and go in and change the value regardless.)

Steps to correct this:

  • Remove the project_name field from the Form class Meta. (Yes, that means replacing the __all__ with the list of fields you want editable.)

  • Either:

    • Change the attribute of this field from readonly to disabled
    • Change this from an input field to just a block of text

(And yes, seeing this explains now why having the to_field attribute on the model allowed this form to be considered “valid”, despite the fact that it wasn’t going to work.)