I'm working with a CreateView where I know what some of the field values will be ahead of time. In the example below, I know that the author field for a new Entry object will be the current user and I use get_initial() to preset this.
Now I would like to omit this field from my template form. I've tried several approaches:
Simply commenting out the author field in the form template leads to an invalid form.
Leaving 'author' out of fields. Nope.
And here's a related problem. The example below involves a relationship to a User object that exists. But what if I need to create an object, say an auth Group for editors? I've tried creating a placeholder group and renaming it ... and, well, that didn't work very well.
#
# model
#
class Author(Model):
name = CharField(max_length = 60)
user = OneToOneField(to = User, related_name = 'author_user', on_delete = CASCADE)
class Entry(Model):
title = CharField(max_length = 60)
author = ForeignKey(to = Author, related_name = 'entry_author')
#
# view
#
class EntryCreateView(CreateView):
model = Entry
fields = ('title', 'author')
def get_initial(self):
initial = super(EntryCreateView, self).get_initial()
initial['author'] = get_object_or_404(Author, user = self.request.user)
return initial
#
# template
#
{% extends "base.html" %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
<label for="{{ form.title.id_for_label }}">Title:</label>
{{ form.title }}
<label for="{{ form.author.id_for_label }}">Author:</label>
{{ form.author }}
<p>
<input type="submit" class="btn btn-primary" name="save" value="Save" />
<input type="submit" class="btn btn-primary" name="cancel" value="Cancel" />
</form>
{% endblock %}
You can manually set user in form_valid() method of EntryCreateView class:
class EntryCreateView(CreateView):
model = Entry
fields = ('title',)
def form_valid(self, form):
user = self.request.user
form.instance.user = user
return super(EntryCreateView, self).form_valid(form)
You'll need to create a ModelForm for the customizations you need (https://docs.djangoproject.com/en/1.9/topics/forms/modelforms/).
You can't remove author because it's required on your model currently.
Try something like this:
In forms.py...
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ['title', 'author']
def __init__(self, *args, **kwargs):
initial = kwargs.get('initial', {})
self.author = initial.get('author')
super(EntryForm, self).__init__(*args, **kwargs)
You can make modifications to the fields (set to not required, delete a field from the form fields, etc) in __init__ or on the class.
Just import and reference this form in your views to use it.
Related
This is in my template:
<form hx-post="{% url 'orders:create' %}">
{% csrf_token %}
{% for service in venue.services.all %}
<input type="checkbox" name="services" value="{{ service.id }}"> {{ service.name }}<br>
{% endfor %}
<button
hx-include="[name='id']"
type="submit"
class="btn btn-primary btn-lg ">
<input type="hidden" value="{{ venue.id }}" name="id">
Submit
</button>
</form>
And this is the view:
class OrderCreateView(CreateView):
model = Order
form_class = OrderForm
template_name = "orders/order_page.html"
success_url = reverse_lazy("orders:success")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["venues"] = Venue.objects.all()
return context
def form_valid(self, form):
if self.request.htmx:
# Get the IDs of the chosen services
service_ids = self.request.POST.getlist('services')
# Set the venue field of the form
form.instance.venue = Venue.objects.get(id=self.request.POST.get("id"))
# Save the form
self.object = form.save()
# Add the chosen services to the Order object
for service_id in service_ids:
service = Service.objects.get(id=service_id)
self.object.chosen_services.add(service)
return super().form_valid(form)
The problem is that the object is being created but only the line with form.instance.venue works, the part where the chosen_services are being added doesn't work, the object is created without any of them.
The service_ids variable is populated with the information from the front end, it has the ids that i need, it just doesn't add them to the object.
This is models.py:
class Order(models.Model):
venue = models.ForeignKey(Venue, on_delete=models.SET_NULL, null=True)
chosen_services = models.ManyToManyField(Service, null=True, blank=True)
Try this code
Here m2m field is already handled by form, you need to just set the value of the venue field with save() method of form
def form_valid(self, form):
if self.request.htmx:
# Get the IDs of the chosen services
service_ids = self.request.POST.getlist('services')
fm = form.save()
# Set the venue field of the form
fm.venue = Venue.objects.get(id=self.request.POST.get("id"))
fm.save()
return super().form_valid(form)
The problem was that in forms.py i had this:
class OrderForm(forms.ModelForm):
class Meta:
model = Order
fields = ["chosen_services"]
I deleted that and now it works!
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
I was following the post, when I try to solve my solution but it doesn't work for me.
My problem:
I have Django Model object with 4 attributes.
Let's say I have something like this
class MyModel(models.Model):
user = models.ForeignKey('auth.User', on_delete=models.CASCADE)
first_at = models.IntegerField()
second_at = models.IntegerField()
third = models.TextField()
Then I have some view in which I would like to present the form with this object. But, I would like the user to only insert 2 of them, user and the third parameter need to be filled automatically; user from current user and third from another HTML element (in which row the form was filled).
class HomePage(LoginRequiredMixin, generic.CreateView):
template_name = 'index.html'
model = MyModel
fields = ('first', 'second')
success_url = reverse_lazy('home')
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.save()
return super().form_valid(form)
Then I have in HTML created
<form action="." method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="hidden" name="third" value="{{ item.third }}" />
<input type="submit" value="submit" />
</form>
Does anyone know why this is not working? And or how should I make it work? Is there is a better way?
Marko
You can try this approach
Create a custom form under a forms.py and exclude the fields let's say
class UserForm(forms.ModelForm):
class Meta:
model=Mymodel
exclude = ['user','third']
and in your create view import the form recenlty created and the use it into the CreateView with form_clas
class HomePage(LoginRequiredMixin, generic.CreateView):
form_class = UserForm
template_name = 'index.html'
model = MyModel
fields = ('first', 'second')
success_url = reverse_lazy('home')
I found the solution,
The problem is, that when you use the generic.CreateView and set either fields, or form_class, then this class will only read those fields. Even if you add manually additional input field it will not be passed inside class.
To solve this problem I created form class with hidden field.
class MyForm(forms.ModelForm):
class Meta:
model = models.OrderDetail
fields = ('first', 'second', 'third')
widgets = {
'third' : forms.HiddenInput(),
}
Then I use this form as normal inside view class
class HomePage(LoginRequiredMixin, generic.CreateView):
template_name = 'index.html'
model = MyModel
form_class = forms.MyForm
success_url = reverse_lazy('home')
Then add additional hidden field inside HTML file
<form action="." method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="hidden" name="third" value="someValue" />
<button type="submit" name="_submit" class="btn btn-raised btn-primary">Submit</button>
</form>
This way, you replace your hidden field that come with form.as_p with custom one.
Note: You don't really replace field. If you inspect the code, you will see, that you will have 2 hidden fields, but only one with value. But because both are hidden, no one could add value to another, so the behaviour is the same as the field would be replaced.
I`m using Django 1.9 with the following:
views.py:
def a_new(request):
# submit button
if request.method == "POST":
form = AForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.save()
return redirect('ui:config-list')
# first visit
else:
form = AForm()
template = 'api/test_template.html'
context = RequestContext(request, {'form': form})
return render_to_response(template, context)
forms.py:
class AForm(forms.ModelForm):
b= forms.ModelMultipleChoiceField(
queryset=B.objects.all(),
widget=FilteredSelectMultiple("b", is_stacked=False)
)
class Meta:
model = A
fields = ('name', 'b', )
test_template.html
...
<form role="form" class="form-inline" action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div>
{{ form.name }}
{{ form.b }}
</div>
<div class="dl-horizontal">
<button type="submit" class="btn btn-success">Save</button>
</div>
</form>
models.py:
class A(models.Model):
name = models.CharField(max_length=100)
b = models.ManyToManyField(B, null=True, blank=True, name='b')
...
The problem is that when I use the Save button - only the name field is created in the item. The b item, that the widget is being used on, is just not being saved to the DB with the new item A created(it does show error if I leave the b field empty so I guess its checking it too).
What can be the problem?
See the documentation about using commit=False when you have a many-to-many field.
Note that there is no reason for you to be using commit=False here though. Remove it, and the second save, and Django will save your values directly.
b is ManyToManyField, it does on saved on the table A. In a different table the relation between A and B is maintained.
If you have an M2M field and do save(commit=False) you need to call this at the end:
form.save_m2m()
See the docs.
At the moment I have some Posts to show to the users. The GenericView handling this page is a DetailView and I've already passed FormMixin into it to handle Comment functionality.
Now I want to add a Flag or Report form to bottom of each Post. I've found some ways to pass two different forms to a single generic view, but I found them messy and django suggest not to do such complex things. My question is how would I do this task?
Actually I was trying to render the form manually but I couldn't figure out how to pass reason id to the action of the form.
Report's Model:
class Report(models.Model):
reporter = models.ForeignKey(User, related_name='reporters')
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
object = GenericForeignKey('content_type', 'object_id')
reason = models.IntegerField(choices=REASON_CHOICES, default=BROKEN_LINK)
Report's Form:
BROKEN_LINK, RUDE, BAD_TRANSLATE, IRR = range(4)
REASON_CHOICES = (
(BROKEN_LINK, 'Broken_link'),
(RUDE, 'Rude'),
(BAD_TRANSLATE, 'Bad_translate'),
(IRR, 'Irr')
)
class ReportForm(forms.ModelForm):
class Meta:
model = Report
fields = ['reason']
widgets = {
'reason': forms.RadioSelect,
}
Report views.py:
( report_object and ReportCreateView are doing the same job, the first one was for the time that I decide to don't use generic views to create handle the form )
def report_object(request, model, object_id, reason):
if request.post == POST:
...
class ReportCreateView(CreateView):
model = Report
form_class = ReportForm
template_name = "forms/report_form.html"
def form_valid(self, form):
...
In textdeatilview I define reasons as:
context['reasons'] = REASON_CHOICES
Here's what I was thinking to do inside template:
<form class="report" method="POST" id="{{ post.id }}" action="{% url 'report_create' model="post" object_id=post.id reason=??? %}">
{% csrf_token %}
{% for id, value in reasons %}
<p><input type="radio" name="reason" id="" value="{{ id }}" />{{ value }}</p>
{% endfor %}
<input type="submit" value="Add">
</form>
Is this the right approach? If so, how should I fix it?
No. The reason ID is part of the submitted form data, it's not part of the URL and it doesn't need to be a parameter to the view.