Check if record exists in model

Hi,

I’m trying to work out how i can check a record exists in 1 model and then if true render a specific template.

There are 2 models. Project and Fundamentals. The Fundamentals has a FK to the projects model.

class Project(models.Model):

    project_name = models.CharField(max_length=50, blank=False, unique=True)
    project_website = models.URLField(max_length=50, blank=True)
    project_description = models.TextField(blank=True)
    ckeditor_classic = models.TextField(blank=True)
    project_category = models.CharField(max_length=15, blank=True)
   
    def __str__(self):
        return str(self.project_name)

and

class Fundamentals(models.Model):

    project_name = models.ForeignKey(Project, to_field='project_name', on_delete=models.CASCADE)
    project_roadmap = models.CharField(max_length=25, blank=True)
    project_tier_entry_level = models.CharField(max_length=25, blank=True)
    project_tier_entry_level_notes = models.CharField(max_length=25, blank=True)
    project_rebranded = models.CharField(max_length=25, blank=True)
    
    def __str__(self):
        return str(self.project_name)

If the PK (project_name ) already exists in the fundamentals model i wanted to render a update_fundamentals.html but im not sure how i check if exists.

my view

@login_required
def Add_or_updateFundamentals(request,project_id):
    project = Project.objects.filter(pk=project_id)
    check_exist = Fundamentals.objects.filter(project_name_id=project_id)
    if request.method == 'POST':
        if check_exist:
            form = AddFundamentalsForm(request.POST,instance=project[0])
        else:
            form = AddFundamentalsForm(request.POST)
        if form.is_valid():
            if not check_exist:
                 form.instance.project = project
            form.save()
            return redirect('dashboard.html')
    else:
        if check_exist:
            form = AddFundamentalsForm(instance=project[0])
            return render(request, 'pages/update_fundamentals.html', {'project': project, "form": form})
        else:
            form = AddFundamentalsForm()
            return render(request, 'pages/add_fundamentals.html', {'project': project, "form": form})

But this doesn’t seem to work. Any ideas?

Thanks

See the exists clause.

However, I would suggest that you break this apart into two views, one for create and one for update. It really does simplify this.

(Also, if this is truly a one-to-one relationship, you probably want to model it that way, rather than trying to artificially manage a many-to-one as if it were a one-to-one.)

Thanks Ken. I originally split this into 2 views. Update and Add.

The template uses a drop down list and I was trying to avoid having two options. (Update and Add) maybe there is a better way of doing this. My concern is that the end user might not know if the data already exists and therefore not sure it add or update. So I was thinking the logic could take care of this for them?

Is that the wrong way to do things?

Yes, it’s fine to have your program make that determination - but that determination should be made prior to the view being called.

Is this drop-down part of a form which is being submitted? If so, your POST handler for that form can handle the redirect.

Or is this drop-down handled immediately through an AJAX call? Same idea really - have the endpoint being invoked make the determination.

(I can’t be more specific than this without knowing how they’re getting to that point. What is happening that takes them to that page?)

But, for something like this, you should also want to be aware of race conditions that might occur. What happens if someone goes through this process at the same time as someone else?

More directly, in the simplest case, you could refactor it like this:

def view_function(request, project_id):
    check_exist = ...
    if check_exist:
        return do_update_view(request, project_id)
    else:
        return do_add_view(request, project_id)

This at least eliminates the redundant conditionals within the views.

This is how I do something similar in one of my test Apps.

date_today = timezone.now()
calendar_object = Calendar.objects.get(date_actual=date_today)

test = calendar_object.holiday_id
    calendar_context = {
        'cal_object': calendar_object,
        'hol_object': holiday_object,
        'vol_object': active_holiday_object
    }
    if test == 1:
        template = 'view1.html'
    else:
        template = 'view2.html'
    return render(request, template, calendar_context)

Only real difference is, I’m not rendering for every condition. I’m rendering once at the end and assigning the template to a variable within the conditional statement.

The dropdown list just appears on a project dashboard. Bit like a landing page and from there you can just choose to update or add data.

Would this be what i need to add

def Fundamentals(request, project_id):
    project = Project.objects.filter(pk=project_id)
    check_exist = Fundamentals.objects.filter(project_name_id=project_id)
    if check_exist:
        return update_fundamentals_view(request, project_id)
    else:
        return add_fundamentals_view(request, project_id)

And create the two views, but from the dropdown just link to the Fundamentals view?

If this is correct, what is the way to display the existing values within the form. would i still just use {{form.instance.project_name}}

Thanks

Tom

Since you’re not using Project in this function, you don’t need to execute that query here. The first line of that function can be the check_exist = ... statement.

Your templates, etc, don’t change as a result of this. Those two subordinate functions are functions that happen to take a request and project_id as parameters and return a response. They’re still (logically, if not physically) part of this view. You won’t create separate URLs for them - unless you want to access them directly from other links.

But if I put a dropdown list on a page, nothing is going to happen unless something else happens. (Either a button is pushed or an on-change handler is triggered.) What causes that dropdown list to take you to this page?

I understand this now. One of the issues i am hitting is that i’m manually rendering the html forms rather than using crispy and then im just using JavaScript to POST back to the database. So i think i need to create views for update and add?

I terms of the dropdown, i have just created hrefs so click loads the url /view.

Depending upon what precisely you mean by “create views for update and add”, no, you don’t need to do that if you’re implementing this “quick refactor” solution.

This refactored view isn’t generating any output of its own. It’s going to return whatever the sub functions return - which can be two completely different responses. How those responses get built doesn’t affect anything.

Oh right.

So when i’ve been creating the views, I’ve also assumed that the view should be a dedicated URL, so that for a POST it will send to the https://myapp.com/update_somethingl and then passing in the parameter.

But what you are saying is i can just send the request back to the same view/url and as long as the view is written correctly it will update the database.

@login_required
def add_project(request):
    
    submitted = False
    if request.method == "POST":
        form = AddProjectForm(request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect('/apps/add_project?submitted=True')
    else:
        form = AddProjectForm
        if 'submitted' in request.GET:
            submitted = True
    
    form = AddProjectForm     
    return render(request, 'pages/add_project.html', {'form':form})

# Game Views
@login_required
def update_project(request,project_id):
    project= Project.objects.get(pk=project_id)
    form = AddProjectForm(request.POST or None, instance=project)
    if form.is_valid():
        form.save()
        return redirect('dahsboard.html')
    return render(request, 'pages/update_project.html', {'project': project, "form": form})

So for the update i POST backto the update_project url but i could just POST back to add_project but with logic?

Again, under the assumption that you’re using the refactored view at Check if record exists in model - #4 by KenWhitesell

No, these sub functions are not (necessarily) independent views.

It’s only the view_function that needs to have a URL associated with it.

The sub functions don’t need URLs nor the @login_required decorator. All access to those functions are through view_function. (That is, unless you have other situations where you want to provide direct access to those functions - but that’s a “system architecture” issue and not a “Django” issue.)

If you get to either of these functions through the URL assigned to view_function, then the POST will go back to view_function. If you assign a separate url to update_project, then the POST will return you back to update_project. (POST, by default, returns you to the URL from which you performed the GET.)

This makes things so much nicer. Thanks Ken.

I’ve implemented the refactored view, but its not loading the desired template:

def Fundamentals_view(request, project_id):
    check_exist = Fundamentals.objects.filter(project_name_id=project_id)
    if check_exist:
        return update_fundamentals_view(request, project_id)
    else:
        return add_fundamentals_view(request, project_id)

When i click the link in the dropdown

<a class="dropdown-item" href="{% url 'add_fundamentals' project.id %}">Critical Fundamentals <span class="badge bg-success" id="fundamentals-complete">Complete</span></a>

It loads the same page regardless of the check_exist outcome. So this suggest that the the check statement is incorrect.

Im still a bit confused by this line
check_exist = Fundamentals.objects.filter(project_name_id=project_id)

i think this is checking that the project_name ID being passed into the view as a parameter exists as a project_id within the fundamentals model? but i’m not sure what the _id is doing.?

Sorry if for not getting it, and im missing something.

The request is throwing this exception
ValueError: Field 'id' expected a number but got '...'.

Correct. As mentioned earlier, see the exists clause.

In the Fundamentals model, you have a field named project_name, which is an FK to Project. A reference to project_name is then a reference to that object. Physically, this FK is represented in the database as a column in the table named project_name_id. Since you’re getting that ID passed in to this method as a parameter, you want to compare the integer value of that ID to the integer project_name_id in that table.

What is the definition in your urls.py file for the add_fundamentals url?

Thank for explaination.

My url for add_fundamentals
path('fundamentals/<int:project_id>', view=Fundamentals_view, name="add_fundamentals"),

I tried removing the int: but that returned that same exception

Can you post the complete traceback from that error? (As text, not as a screenshot.) I’m not seeing anything wrong based on what you’ve posted, so I need to keep digging.
Also, can you verify what the URL is that is being generated in your template?
And, what URL is being accessed?

Below is the Traceback

Traceback (most recent call last):
  File "d:\AzDoProjects\webApp\venv\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
    response = get_response(request)
  File "d:\AzDoProjects\webApp\venv\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "d:\AzDoProjects\webApp\venv\lib\site-packages\django\contrib\auth\decorators.py", line 21, in _wrapped_view
    return view_func(request, *args, **kwargs)
  File "D:\AzDoProjects\webApp\Project_WebApp\apps\views.py", line 199, in ShowProjectDetails
    project = Project.objects.get(pk=project_id)
  File "d:\AzDoProjects\webApp\venv\lib\site-packages\django\db\models\manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "d:\AzDoProjects\webApp\venv\lib\site-packages\django\db\models\query.py", line 424, in get
    clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs)
  File "d:\AzDoProjects\webApp\venv\lib\site-packages\django\db\models\query.py", line 941, in filter
    return self._filter_or_exclude(False, args, kwargs)
  File "d:\AzDoProjects\webApp\venv\lib\site-packages\django\db\models\query.py", line 961, in _filter_or_exclude
    clone._filter_or_exclude_inplace(negate, args, kwargs)
  File "d:\AzDoProjects\webApp\venv\lib\site-packages\django\db\models\query.py", line 968, in _filter_or_exclude_inplace
    self._query.add_q(Q(*args, **kwargs))
  File "d:\AzDoProjects\webApp\venv\lib\site-packages\django\db\models\sql\query.py", line 1393, in add_q
    clause, _ = self._add_q(q_object, self.used_aliases)
\db\models\sql\query.py", line 1193, in build_lookup
    lookup = lookup_class(lhs, rhs)
  File "d:\AzDoProjects\webApp\venv\lib\site-packages\django\db\models\lookups.py", line 25, in __init__
    self.rhs = self.get_prep_lookup()
  File "d:\AzDoProjects\webApp\venv\lib\site-packages\django\db\models\lookups.py", line 77, in get_prep_lookup
    return self.lhs.output_field.get_prep_value(self.rhs)
  File "d:\AzDoProjects\webApp\venv\lib\site-packages\django\db\models\fields\__init__.py", line 1825, in get_prep_value
    raise e.__class__(
ValueError: Field 'id' expected a number but got '...'.

Views.py

def Fundamentals_view(request, project_id):
    check_exist = Fundamentals.objects.filter(project_name_id=project_id)
    if check_exist:
        return update_fundamentals_view(request, project_id)
    else:
        return add_fundamentals_view(request, project_id)


@login_required
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('http://127.0.0.1:8000/')
    else:
        form = AddFundamentalsForm()
    return render(request, 'pages/add_fundamentals.html', {'project': project, "form": form})

def update_fundamentals_view(request,project_id):
    project= Project.objects.get(pk=project_id)
    form = AddFundamentalsForm(request.POST or None, instance=project)
    if form.is_valid():
        form.save()
        return redirect('dahsboard.html')
    return render(request, 'pages/update_fundamentals.html', {'project': project, "form": form})


Thank for taking the time Ken. Really appreciate it

In that same area as that error (either immediately above or below the traceback) is the GET for the page that is causing this error. What is the URL being accessed?

Also, check the rendered page in the browser (developer tools or “view source”) to see what the actual href is being rendered for those links.

"GET /show_project_details/... HTTP/1.1" 500 140557

def ShowProjectDetails(request,project_id):
    today = now().date()
    project = Project.objects.get(pk=project_id)
    events = Event.objects.event_list = Event.objects.filter(event_date__gte=today).order_by('event_date')
    form = AddProjectForm(request.POST or None, instance=project)

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

path('show_project_details/<project_id>', view=ShowProjectDetails, name="show_project_details"),

So that’s the issue for that traceback. Whatever is supposed to be building the link to that page is building an invalid link. Note, it’s not likely going to be the page for show_project_details, the problem is in the page with that link to get you to show_project_details.

Ok, the page that takes me to that show_project_details is just a dashboard that has another dropdown list with a href
this is the requested url
Request URL: http://127.0.0.1:8000/show_project_details/1