Making a CreateForm with choices based on database values - django

I am making a django project and I have a form for the User to add a Vehicle Manually that will be assigned to him. I also would like to had an option for the user to choose a vehicle based on the entries already present in the database.
vehicles/models.py
class Vehicle(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
nickname = models.CharField(unique = True, max_length=150)
date_joined = models.DateTimeField(default=timezone.now)
brand = models.CharField(max_length=150)
battery = models.CharField(max_length=150)
model = models.CharField(max_length=150)
def __str__(self):
return self.nickname
def get_absolute_url(self):
return reverse('vehicle-list')
class Meta:
db_table = "vehicles"
I created a form so the user can add his Vehicles as such:
vehicles/forms.py
class VehicleAddFormManual(forms.ModelForm):
class Meta:
model = Vehicle
fields = ('brand','model', 'battery', 'nickname')
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
self.fields['brand']
self.fields['model']
self.fields['battery']
self.fields['nickname']
The corresponding view:
vehicles/views.py
class AddVehicleViewManual(LoginRequiredMixin, CreateView):
model = Vehicle
form_class = VehicleAddFormManual
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
The html file:
vehicles/templates/vehicles/vehicle_form.html
{% extends "blog/base.html" %}
{% block content %}
{% load crispy_forms_tags %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">New Vehicle</legend>
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Submit</button>
</div>
</form>
</div>
{% endblock content %}
I would like to add another form in which the user has a dropdown with option with the brands, models and batteries that already exist in the database. If there's a car in the database with brand: Tesla, model: Model 3, battery: 50 kWh, then it would appear in the dropbox as a choice for each field.
I'm not sure how to do this and sorry for the newbie question... Thanks in advance!

I once had to do something similar, but I needed a form which had one checkbox for each item in a list of externally-supplied strings. I don't know if this is the cleanest way, but I used python metaclasses:
class SockSelectForm(forms.Form):
#staticmethod
def build(sock_names):
fields = {'sock_%s' % urllib.parse.quote(name):
forms.BooleanField(label=name, required=False)
for name in sock_names}
sub_class = type('DynamicSockSelectForm', (SockSelectForm,), fields)
return sub_class()
In my get() method, I instantiate it as:
form = SockSelectForm.build(names)
and the corresponding form handling in the post() method is:
form = SockSelectForm(request.POST)
I suspect if you look under the covers of Django's ModelForm, you'd see something similar, but I couldn't use ModelForm because it's too closely tied to the model system for what I needed to do.

model.py
class DropdownModel(models.Model):
brand = models.CharField(max_length=150)
battery = models.CharField(max_length=150)
model = models.CharField(max_length=150)
def __str__(self):
return self.brand.
form.py
from .models import DropdownModel
all_brand = DropdownModel.objects.values_list('brand','brand')
all_battery = DropdownModel.objects.values_list('battery','battery')
all_model= DropdownModel.objects.values_list('model','model')
class DropdownForm(forms.ModelForm):
class Meta:
model = DropdownModel
fields = "__all__"
widgets = {
'brand':forms.Select(choices=all_brand),
'battery':forms.Select(choices=all_battery),
'model':forms.Select(choices=all_model),
}
view.py
from django.shortcuts import render
from .form import DropdownForm
# Create your views here.
def HomeView(request):
form = DropdownForm()
context = {'form':form}
return render(request,'index.html',context)
index.html
{% extends "base.html" %}
{% load static %}
{% block title %}
Index | Page
{% endblock title %}
{% block body %}
{{form.as_p}}
{% endblock body %}
Output-
Note- if u can't see updated values in dropdown do server restart because localhost not suport auto update value fill in dropdown it's supoorted on live server
Thank you

Related

Foreign key is not assigned to a model in database

I recently started learning django and was making a CRM.
models.py:
class Complaint(models.Model):
SOURCE_CHOICES=(
('email','E-mail'),
('call','Call'),
('ticket','Ticket')
)
store_name=models.CharField(max_length=20)
store_contact_no=models.IntegerField(max_length=10)
store_id=models.CharField(max_length=7)
source=models.CharField(choices=SOURCE_CHOICES, max_length=10)
agent=models.ForeignKey("Agent", null = True, blank = True, on_delete=models.SET_NULL)
category=models.ForeignKey("Category", related_name="complaints", null = True, blank = True, on_delete=models.SET_NULL)
description = models.TextField()
customer = models.ForeignKey("Customer", null = True, blank = True, on_delete=models.SET_NULL)
def __str__(self):
return f"{self.store_name} {self.store_id}"
class Customer(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
def __str__(self):
return self.user.username
forms.py
class CustomerComplaintForm(forms.ModelForm):
class Meta:
model = Complaint
fields = (
'store_name',
'store_id',
'store_contact_no',
'description',
)
views.py
class
CustomerComplaintCreateView(OwnerCustomerAndLoginRequiredMixin,
generic.CreateView):
template_name = "customercomplaint_create.html"
form_class = CustomerComplaintForm
def get_success_url(self):
return "/complaints"
def form_valid(self, form):
complaint = form.save(commit=False)
complaint.Customer = self.request.user.username
complaint.source = 'ticket'
complaint.save()
return super(CustomerComplaintCreateView,
self).form_valid(form)
html template:
{% extends "base.html" %}
{% load tailwind_filters %}
{% block content %}
<div class="max-w-lg mx-auto">
<a class="hover:text-blue-500" href="/complaints">Go back to complaints </a>
<div class="py-5 border-t border-gray-200">
<h1 class="text-4xl text-gray-800"> Create a new complaint </h1>
</div>
<form method='post' class="mt-5">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="w-full bg-blue-500 text-white hover:bg-blue-600 px-
3 py-2 rounded-md">Create</button>
</form>
</div>
{% endblock content %}
mixins.py
class OwnerCustomerAndLoginRequiredMixin(AccessMixin):
"""Verify that the current user is authenticated and is an owner or customer"""
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated or not request.user.is_owner and not request.user.is_customer:
return redirect("/complaints")
return super().dispatch(request, *args, **kwargs)
The problem here is that, the source field gets filled in the database with Ticket as intended. But the 'Customer' field is not populated with the username. 'Self.request.user.username' is not the problem here as the username is being printed correctly in the console.
The issue is complaint.Customer = self.request.user.username, you're trying to assign a username to a supposedly Customer object. Here's an approach you could take to solve the issue though.
Within the views.py file, the view class.
You could get the customer object and then assign it to the customer field on the complaint object.
from django.shortcuts import get_object_or_404
def form_valid(self, form):
complaint = form.save(commit=False)
customer = get_object_or_404(Customer, user=self.request.user) # recommended
# or
# customer = get_object_or_404(Customer, user__username__iexact=self.request.user.username)
if customer:
# Here on your model you have a lowercase `c` for the customer field, not 'C`
complaint.customer = customer # -> This will assign the customer object, "FK".
complaint.source = 'ticket'
complaint.save()
return super(CustomerComplaintCreateView, self).form_valid(form)
That should work.
this must be the User not the user name
Because Cutomer is User object not only the Uesrname
def form_valid(self, form):
complaint = form.save(commit=False)
complaint.Customer = self.request.user
complaint.source = 'ticket'
complaint.save()

Passing two Models in update view to a same template

I want to render two models User (built in model) and Profile model to the same template profile_form.html so that the user can update the data of both User model as well as Profile model
This is my Profile model
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.png', upload_to='profile_pics')
description = models.TextField()
def __str__(self):
return self.user.username + "'s Profile"
This is my profile_form.html
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block title %}
Make your Profile
{% endblock title %}
{% block content %}
<div class="container mb-6">
<form action="" method="POST" class="form-group">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn btn-success">Submit</button>
</form>
</div>
{% endblock content %}
This is my UserUpdateView
class UserUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model=User
fields=['username', 'first_name', 'last_name']
success_url='/'
def test_func(self):
x = self.request.user.id
y = self.kwargs['pk']
if x == y:
return True
else:
if self.request.user.is_authenticated:
raise Http404("You are not authenticated to edit this profile")
I want my Profile model's to be below User model's form
Please help me with this
To add a OneToOne-relation into the same view, you just need to overwrite the get_context_data method and provide an additional form.
If you don't have a profile form yet, just create a simple one:
#yourapp/forms.py
class ProfileForm(ModelForm):
class Meta:
model = Profile
fields = "__all__"
Now, to use this in your updateview, import it and you will need to change it like this:
class UserUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model=User
fields=['username', 'first_name', 'last_name']
success_url='/'
# create context manually
def get_context_data(self, **kwargs):
data = super(UserUpdateView, self).get_context_data(**kwargs)
if self.request.POST:
data["profile"] = ProfileForm(self.request.POST)
else:
# accessing the profile object
data["profile"] = ProfileForm(instance=self.object.profile)
return data
And now your template will have access to the context profile
...
{{ form|crispy }}
{{ profile|crispy }}
...

django class based view pass url parameter in create post to current category

lets's say that i have three categories (tutorials, news, jobs).
and i have class based views to list all posts, list posts by category and create new posts.
and sure post is the same model and fields to all categories.
my problem is :
if user was in category list template (let's say tutorial) .. i want the user when he create new post .. it is saved directly to tutorial category .. and if user was in list template (let's say news) .. he will create new post which will be saved directly to news category.
i mean create new post saved directly to current category.
i believe i will use (pass url parameter to class based views) but actually i failed to do that .. and i searched tonnage of questions without got what i want.
can any body help .. with sample please.
models.py
class Category(models.Model):
name = models.CharField(max_length=50)
slug = models.SlugField(max_length=50, unique=True)
def save(self, *args, **kwargs):
if not self.slug and self.name:
self.slug = slugify(self.name)
super(Category, self).save(*args, **kwargs)
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
views.py
def PostListView(request, category_slug=None):
category = None
posts = Post.objects.all().prefetch_related().annotate(commentscountperpost=Count('comments'))
categories = Category.objects.prefetch_related().annotate(total_product_category=Count('post'))
if category_slug:
category = Category.objects.get(slug=category_slug)
posts = posts.filter(category=category)
context = {
'title': 'Home Page',
'posts': posts,
'total_posts': total_posts,
'categories': categories,
'category': category,}
return render(request, 'blog/index.html', context)
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
template_name = 'blog/new_post.html'
form_class = PostCreateForm
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
forms.py
class PostCreateForm(forms.ModelForm):
title = forms.CharField(label='Title')
content = forms.CharField(label='Content', widget=forms.Textarea)
class Meta:
model = Post
fields = ['title', 'content']
urls.py
path('index_list/', PostListView, name='list'),
path('<slug:category_slug>', PostListView, name='post_category_list'),
path('new_post/', PostCreateView.as_view(), name='new_post'),
new_post.html template
{% extends 'base.html' %}
{% block content %}
{% load crispy_forms_tags %}
<div class="border p-4 mb-5">
<legend class="border-bottom pb-1 mb-3">New Post </legend>
<form method="POST">
{% csrf_token %}
{{form|crispy}}
<input class="btn btn-secondary mt-4" type="submit" value="Add New Post">
</form>
</div>
{% endblock content %}
list.html template
{% for category in categories %}
<h5><a class="text-primary" href="{% url 'post_category_list' category.slug %}">
{{ category.name }} ({{ category.total_product_category }})</a></h5>
{% endfor %}
<h5>{{ total_posts }} Total Posts </h5>
{% if category %}
New {{ category }}
{% endif %}
You can try like this:
class PostCreateView(generic.CreateView):
model = Post
template_name = 'blog/new_post.html'
form_class = CreatePostForm
slug_url_kwarg = 'slug'
def form_valid(self, form):
category = Category.objects.get(slug=self.kwargs['slug'])
form.instance.category = category
form.instance.author = self.request.user
return super(PostCreateView, self).form_valid(form)
And in the urls
path('new_post/<slug>/', PostCreateView.as_view(), name='new_post'),
yes i found it , depending on arjun answer, greate thanks for arjun
in list posts per category template change :
New {{ category }}
to:
New {{ category }}
and it works fine,
thanks.

How can I grab the (text) value instead of the number on a multiple choice field?

I have a CreateView class and I'm trying to use the input from a multiple choice field it has as part of the success_url.
It works on my TopicCreateView class because the input is a charfield, but when I try to get it to work on PostCreateView it returns a KeyError. From what i understand it's because it returns the value of the multiple choice field(1, 2, 3 etc) instead of the text between the option tags.
The Topic part works fine.
views.py
class TopicCreateView(LoginRequiredMixin, CreateView):
model = Topic
template_name = 'topic_form.html'
fields = ['board', 'title']
success_url = '/topic/{title}'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
And here is the models.py
class Topic(models.Model):
title = models.CharField(max_length=100, unique=True)
board = models.ForeignKey(Board, default='ETC', on_delete=models.SET_DEFAULT)
date_published = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.title
Here is what I can't get to work, the Post.
views.py
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
template_name = 'post_form.html'
fields = ['topic', 'content']
success_url = '/topic/{topic}'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
and models.py
class Post(models.Model):
content = models.TextField()
author = models.CharField(max_length=200, default='Unknown', blank=True, null=True)
date_published = models.DateTimeField(default=timezone.now)
topic = models.ForeignKey(Topic, default=content, on_delete=models.SET_DEFAULT)
def __str__(self):
return self.topic
Also, the form is the same for both of them:
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Create A New Post</legend>
{{ form | crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Submit</button>
</div>
</form>
</div>
{% endblock %}
So, when I redirect to the newly created topic/thread it works, but I can't do the same for new posts.

Setting a parents value in a childs object's model form in Django?

I wish for one of a parent's variables to be pre-populated in a child's model form specifically a serial number. I have managed to get the serial number as part of the URL but would like to figure out how it can be implemented as a variable on the form page.
Models.py
class Product(models.Model):
serial_number = models.CharField(unique=True, max_length=15)
class ProductInstance(models.Model):
serial_number = models.ForeignKey('Product', on_delete=models.SET_NULL, null=True)
Views.py
class ProductInstanceCreate(CreateView):
model = ProductInstance
template_name = 'myapp/edit_productinstance.html'
form_class = GunInstanceForm
def get_success_url(self):
return reverse_lazy ('product-detail', kwargs={'pk': self.object.serial_number.pk})
Forms.py
class ProductInstanceForm(forms.ModelForm):
class Meta:
model = ProductInstance
fields = '__all__'
templates/myapp/product_detail.html
...
New
...
urls.py
urlpatterns += [
url(r'^productinstance/(?P<serial_number>[-\w]+)/create/$', views.ProductInstanceCreate.as_view(), name='productinstance_create'),]
templates/myapp/edit_productinstance_form.html
{% extends "base_generic.html" %}
{% block content %}
<h2>Serial Number: {{ serial_number }}</h2>
</br>
<form action="" method="post">
{% csrf_token %}
<table>
{{ form }}
</table>
<input type="submit" value="Submit" />
</form>
</br>
Back
{% endblock %}
So currently I can create a URL such as: productinstance/D1430913/create/
I now need to know:
How to use it as a variable for the title?
How to set the the forms default value to it?
For the title:
<h2>Serial Number: {{ form.serial_number.value }}</h2>
I believe if you modify your CreateView like so and implement the serial_number_func() with whatever you need to get the serial number, this will do what you want:
class ProductInstanceCreate(CreateView):
model = ProductInstance
template_name = 'myapp/edit_productinstance.html'
form_class = GunInstanceForm
def get_form_kwargs(self):
kwargs = super(ProductInstanceCreate, self).get_form_kwargs()
kwargs['serial_number'] = serial_number_func()
return kwargs