How to implement an autocomplete-datalist form field inside a CreateView in Django? - django-views

I am very new to web development and specifically using Django Framework.
I am trying to find a clean, efficient and non external package dependant implementation for an autocomplete-datalist form field inside a Generic class based CreateView template in Django.
I have found numerous resources on various implementations, but most of them depend on external packages(autocomplete-light, jqueryCDN, etc.) and none of it is based on a class based generic CreateView.
I have been experimenting and I have managed to make the autocomplete-datalist work in a way but I am stuck in a very simple problem when I try to post my form with the data from the datalist element.
I get a validation error:
"city_name: This field is required"
I also noticed that the city object queried from the database inside the datalist has also the id of the city_name
models.py
from django.db import models
class City(models.Model):
name = models.CharField(max_length=50)
class Meta:
verbose_name_plural = "cities"
ordering = ['name']
def __str__(self):
return self.name
class Person(models.Model):
first_name = models.CharField(max_length=40)
last_name = models.CharField(max_length=40)
address = models.CharField(max_length=150)
city_name = models.ForeignKey(City, on_delete=models.CASCADE)
def __str__(self):
return f'{self.first_name} {self.last_name}'
views.py
from django.views.generic import ListView, CreateView
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import Person, City
from .forms import PersonForm
# Create your views here.
class PersonList(LoginRequiredMixin, ListView):
model = Person
template_name = "home.html"
paginate_by = 20
login_url = "/login/"
redirect_field_name = 'redirect_to'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
return context
class PersonCreate(LoginRequiredMixin, SuccessMessageMixin, CreateView):
model = Person
template_name = "testform.html"
login_url = "/login/"
form_class = PersonForm
success_url = 'testapp/add/'
success_message = 'Person registered successfully!'
redirect_field_name = 'redirect_to'
forms.py
from django import forms
from .models import Person, City
class PersonForm(forms.ModelForm):
class Meta:
model = Person
fields = ["first_name", "last_name", "address", "city_name"]
testform.html
{% extends 'home.html' %}
{% load static %}
{% block content %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-success alert-dismissible fade show" role="alert">
<span style="font-size: 18px;padding: 1mm"><i class="fa-solid fa-circle-check"></i></span>{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
<form method="POST">
{% csrf_token %}
<div class="mb-3">
<label for="first_name charfield" class="form-label"> First Name</label>
{{form.first_name}}
</div>
<div class="mb-3">
<label for="last_name charfield" class="form-label">Last Name</label>
{{form.last_name}}
</div>
<div class="mb-3">
<label for="address charfield" class="form-label">Address</label>
{{form.address}}
</div>
<div class="mb-3">
<label for="city_name datalist" class="form-label">City Name</label>
<input type="text" list="cities" class="form-control">
<datalist id="cities">
{% for city in form.city_name %}
<option>{{ city }}</option>
{% endfor %}
</datalist>
</div>
<button class="btn btn-outline-primary" type="submit">Submit</button>
</form>
{{form.errors}}
{% endblock %}
Result:
I believe it is a necessary feature for all modern web applications to have this kind of functionality within their database query-autocomplete form fields system. It is a pity that although Django provides this feature for the AdminModels through its autocomplete_fields attribute, it makes it so hard to implement on Generic Class Based Views on the actual application models.
How can I approach this issue, and is there a efficient and more optimized way to implement it?

If you don't want a field required you can set the attribute blank=True in the model class. A question I have is why would you want to have a Foreignkey to just a city name. Or are you trying to use the a list of cities to populate the drop down? In that case the Foreign Key is definitely not the answer.

Related

How do I display my form in a Django template?

I am trying to display my stat update form in my Django template, however it isn't displaying. My stats below show up correctly, just not the form.
{{ stats.user }} | {{ stats.weight }} | {{ stats.date }}
Template:
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-sm-12 text-center">
<h1>My Health</h1>
</div>
</div>
</div>
<div class="container-fluid">
<div class="col-auto text-center p-3 form-group">
<form method="post" style="margin-top: 1.3em;">
{{ update_form }}
{% csrf_token %}
<button type="submit" class="btn btn-signup btn-lg">Submit</button>
</form>
</div>
<div class="row justify-content-center">
<div class="col-auto text-center p-3">
<p class="text-center"> {{ stats.user }} | {{ stats.weight }} | {{ stats.date }} </p>
</div>
</div>
</div>
{% endblock content %}
forms.py:
class StatUpdateForm(forms.Form):
class Meta:
model = Stats
fields = ('user', 'weight', 'date')
models.py:
class Stats(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
date = models.DateField(auto_now=True)
weight = models.DecimalField(max_digits=5, decimal_places=2)
class Meta:
db_table = 'health_stats'
ordering = ['-date']
def __str__(self):
return f"You currently weigh {self.weight}, {self.user}"
views.py:
from django.shortcuts import render
from .models import Stats
from .forms import StatUpdateForm
from django.views import generic, View
from django.shortcuts import get_object_or_404
# from django.contrib.auth import authenticate
# from django.core.exceptions import ValidationError
# from .forms import RegistrationForm, LoginForm
def home(request):
return render(request, 'home.html')
class MyHealth(View):
def get(self, request, *args, **kwargs):
stats = Stats
context = {
'stats': stats,
"update_form": StatUpdateForm(),
'user': stats.user,
'weight': stats.weight,
'date': stats.date,
}
return render(request, 'MyHealth.html', context)
I've tried redefining the form in my views.py, but I'm unsure why it isn't pulling through, as the other parts of the context are.
Any help would be appreciated!
In the form, since you are using a model, you must extend from forms.ModelForm instead forms.Form, try to change that line in the forms.py
class StatUpdateForm(forms.ModelForm): # extends from forms.ModelForm
class Meta:
model = Stats
fields = ('user', 'weight', 'date')
I assume the form fields are rendering and the fields just aren't loaded with the correct values.
do this: StatUpdateForm(instance=stats) to make it an edit form

DJANGO: Save two forms within one class-based view

I have difficulties saving 2 forms within one view the only first one is saving but I cant figure out how to save the second one. Here is my code and issue :
models.py
class Office(models.Model):
name = models.CharField(max_length=100,null=True)
Address = models.ForeignKey(Address, on_delete=models.CASCADE, related_name='officeAddress',blank=True,null=True)
def __str__(self):
return self.name
class Address(models.Model):
address_line = models.CharField(max_length=60, blank=True)
address_line2 = models.CharField(max_length=60, blank=True)
country = models.ForeignKey(Country, on_delete=models.CASCADE, related_name='District')
province=ChainedForeignKey(Province,chained_field="country",chained_model_field=
"country",show_all=False,auto_choose=True,sort=True)
district=ChainedForeignKey(District,chained_field="province",
chained_model_field="province",show_all=False,auto_choose=True,sort=True)
class Meta:
verbose_name = "Address"
verbose_name_plural = "Addresses"
forms.py
class OfficeModelForm(BSModalModelForm):
class Meta:
model = Office
fields = ['name']
class AddressForm(forms.ModelForm):
class Meta:
model = Address
fields = ['address_line','address_line2','country','province','district']
views.py
class OfficeCreateView(BSModalCreateView):
form_class = OfficeModelForm
second_form_class = AddressForm
template_name = 'settings/create_office.html'
success_message = 'Success: Office was created.'
success_url = reverse_lazy('company:office-list')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['address'] = self.second_form_class
return context
create_office.html
{% load static i18n %}
{% load widget_tweaks %}
<form method="post" action="" enctype="multipart/form-data">
{% csrf_token %}
{{ address.media.js }}
<div class="modal-body">
<div class="form-group">{% render_field form.name %}</div>
<div class="form-group">{% render_field address.address_line %}</div>
<div class="form-group">{% render_field address.address_line2 %}</div>
<div class="form-group">{% render_field address.country %}</div>
<div class="form-group">{% render_field address.province %}</div>
<div class="form-group">{% render_field address.district %}</div>
<button class="btn btn-primary ms-auto" type="submit">{% trans "Create new office" %}</button>
</div>
</form>
I think I need first to save the address then use the address.id as a foreign key for office but I don't know how to do this in CBV.
Thanks for your help...
You should change models base names. Example:
class OfficeCreateView(CreateView) instead class OfficeCreateView(BSModalCreateView)
or
class OfficeModelForm(form.ModelForm) instead class OfficeModelForm(BSModalModelForm)

How do I pass the value of two separate inputs into the content field of my form in Django?

In my template I have a form that includes two input elements whose values can be adjusted with javascript. I want to be able to take these values and, on form submit, display them in a sentence in a for loop underneath.
index.html:
<form action="{% url 'workouts:workout' %}" method="post">
{% csrf_token %}
<div class="weight">
<h4>WEIGHT (kgs):</h4>
<button type="button" class="weight-dec">-</button>
<input type="text" value="0" class="weight-qty-box" readonly="" name="one">
<button type="button" class="weight-inc">+</button>
</div>
<div class="reps">
<h4>REPS:</h4>
<button type="button" class="rep-dec">-</button>
<input type="text" value="0" class="rep-qty-box" readonly="" name="two">
<button type="button" class="rep-inc">+</button>
</div>
<input type="submit" value="Save" name="submit_workout">
<input type="reset" value="Clear">
</form>
{% if exercise.workout_set.all %}
{% for w in exercise.workout_set.all %}
{{ w.content }}
{% endfor %}
{% endif %}
I have given the form above an action attribute for a url which maps to a view, and each of the inputs has a name in order to access their values in the view. I also have written this form in forms.py:
class WorkoutModelForm(forms.ModelForm):
class Meta:
model = Workout
fields = ['content']
And for context, here is my model:
class Workout(models.Model):
content = models.CharField(max_length=50)
created = models.DateField(auto_now_add=True)
updated = models.DateField(auto_now=True)
exercise = models.ForeignKey(Exercise, on_delete=models.CASCADE, default=None)
class Meta:
ordering = ('created',)
My problem from here is that I have no idea how to actually incorporate my model form in my template, or how to write a view that will do what I want it to. I am still new to this and have been searching for an answer for sometime, but so far have not found one. Please help.
This is able to help you, you should first have a look at the django Class-Based Views , more specifically the FormView, django already has generic views capable of handling data posted on forms. Your code would look like this:
# forms.py
# imports ...
class WorkoutModelForm(forms.ModelForm):
class Meta:
model = Workout
fields = ['content']
# urls.py
from django.urls import path
from . import views
app_name = 'myapp'
urlpatterns = [
path("test-form/", views.TesteFormView.as_view(), name='test-form'),
]
# views.py
from django.views.generic import FormView
from myapp import forms
from django.contrib import messages
class TesteFormView(FormView):
template_name = "myapp/index.html"
success_url = reverse_lazy('myapp:test-form')
form_class = forms.WorkoutModelForm
def get(self, request, *args, **kwargs):
return super(TesteFormView, self).get(request, *args, **kwargs)
def form_valid(self, form):
print(f"POST DATA = {self.request.POST}") # debug
content = form.cleaned_data.get('content')
# fieldx= form.cleaned_data.get('fieldx')
# do something whit this fields like :
Workout.object.create(content=content)
messages.success(self.request,"New workout object created")
return super(TesteFormView, self).form_valid(form=self.get_form())
def form_invalid(self, form):
print(f"POST DATA = {self.request.POST}") # debug
for key in form.errors:
messages.error(self.request, form.errors[key])
return super(TesteFormView, self).form_invalid(form=self.get_form())
And your template would look like:
# myapp/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>TestForm</title>
</head>
<body>
<form method="post">
{% csrf_token %}
{{ form }}
<button type="submit">submit</button>
</form>
</body>
</html>

Django CreateView form not getting inserted

I'm fairly new to this, but what I'm trying to do is get my form to display (injected as part of a template) in another view. In developer tools I see the HTML for my included page (polls/_poll_form.html), but not the form. I would appreciate it if someone could point me in the right direction.
models.py
class Poll(models.Model):
poll_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255, unique=True)
topic = models.ForeignKey(
Topic,
related_name = 'polls',
on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
last_updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse(
'polls:single',
kwargs={'pk':self.pk}
)
class Meta:
db_table = 'polls'
ordering = ['last_updated_at']
views.py
class CreatePoll(LoginRequiredMixin, generic.CreateView):
template_name = 'polls/_poll_form.html'
model = Poll
_poll_form.html (injected template)
<div class="container poll-form-header">
<p class="text-center">Get Started</p>
</div>
<form class="create-poll-form" action="{% url 'topics:single' pk=topic.topic_id %}" method="POST">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit" class="btn btn-dark float-right">
</form>
topic_detail.html
{% extends "topics/topic_base.html" %}
{%block topics_content %}
<div class="col-md-12 list-group polls-list">
<div class="container new-poll-button">
<a class = "btn btn-dark float-right mt-2" data-toggle="collapse" href="#poll-form" role="button" aria-expanded="false">Create Poll</a>
</div>
<div class="collapse mt-2 new-poll-form" id="poll-form">
<div class="card card-body">
{% include "polls/_poll_form.html" %}
</div>
</div>
{% if topic.polls.count == 0 %}
<br>
<div class="container no-polls-message">
<p>There are no polls for this topic. Create the first!</p>
</div>
{% else %}
{% for poll in topic.polls.all %}
{% include "polls/_poll.html" %}
{% endfor %}
{% endif %}
</div>
{% endblock %}
This appears to be a fairly common confusion, but I don't really understand how it arises.
Just including a template in another one doesn't mean that a view which mentions that template is executed. Views render templates, templates don't call views. Views are only called by the user requesting a URL which is handled by that view. In your case, the URL is pointing to a completely different URL, and the one that creates the form is never called.
You need to include the form in the context of the view that your URL is actually calling. Either do this explicitly in the get_context_data method, or - if the form needs to appear on multiple pages - create a custom template tag that inserts a rendered template, including the form.
Make a forms.py in your app.
Write something like this:
from .models import Poll
class PollForm(forms.ModelForm):
class Meta:
model = Poll
fields = ('name', 'topic',)
And then import PollForm in views.py and pass it to template
from polls.forms import PollForm
class CreatePoll(LoginRequiredMixin, generic.CreateView):
template_name = 'polls/_poll_form.html'
model = Poll
form_class = PollForm

Django: Combine DetailView with form

I am currently handling the following situation. I have a DetailView, that based on the URL slug get's data from the database.
What I am currently struggling with is how to add a ModelForm to this DetailView. The form will show different ticket types, and then the user can select the quantity he wants, before clicking on continuing to get on the checkout page (ticket booking platform). The selection will be saved in the database under the ReservedItems model.
Or would it be a better way to have a CreateView and load the DetailView data into that? I am a bit confused what approach is best, and how to do it. Could you guys help me?
Here a picture how it should look like (currently not functional):
Current views.py
class EventDetailView(DetailView):
context_object_name = 'event'
def get_object(self):
organiser = self.kwargs.get('organiser')
event = self.kwargs.get('event')
queryset = Event.objects.filter(organiser__slug=organiser)
return get_object_or_404(queryset, slug=event)
Template:
<h1>{{ event.name }}</h1>
<small class="text-muted">
<em>Presented by {{ event.organiser.name }}</em>
</small>
<div class="card mt-3">
<div class="card-body">
Short description: {{ event.short_description }}
</div>
</div>
<div class="container mt-5">
<form action="{% url 'checkout:reserve_ticket' %}" method="post">
{% csrf_token %}
{{ form }}
<button type="submit" class="btn btn-primary">Continue</button>
</form>
</div>
models.py
class ReservedItem(models.Model):
order_reference = models.CharField(
max_length=10,
unique=True
)
ticket = models.ForeignKey(
Ticket,
on_delete=models.PROTECT,
related_name='reserved_tickets'
)
ticket_name = models.CharField(max_length=100)
quantity = models.IntegerField(default=0)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
DetailView shouldn't restrict what sort of behaviour your view is able to perform. You can add a form to a DetailView by overriding get_context_data like so:
def get_context_data(self, **kwargs):
context = super(EventDetailView, self).get_context_data(**kwargs)
context['form'] = MyFormClass
return context
After this, you can override the post method to handle form submission logic.
Whether you use a CreateView or DetailView is up to you. You may find that CreateView is more convenient for the functionality you're aiming for.