Automatically filled form in class-based view - python

I want to automatically fill some form in class-based view and save in database.
post function in my view
def post(self, request, *args, **kwargs):
form_2 = self.form_class_2(self.request.POST)
if form_2.is_valid():
keyword = form_2.cleaned_data['keyword']
books = self.search(keyword)
if books:
for book in books:
title = self.add_title(book)
form = self.form_class(initial={"title": title})
if form.is_valid():
form.save()
else:
return HttpResponseRedirect(reverse_lazy('add_books'))
return HttpResponseRedirect(reverse_lazy('import_books'))
else:
return HttpResponseRedirect(reverse_lazy('index_books'))
return reverse_lazy('import_books')
my form
class BookForm(forms.ModelForm):
class Meta:
model = Book
exclude = ()
my form_2
class SearchBookForm(forms.Form):
keyword = forms.CharField(max_length=100)
my model
class Book(models.Model):
title = models.CharField(
max_length=75,
verbose_name='Book title')
published_date = models.CharField(
max_length=10,
validators=[check_if_value_is_date, max_year_validator],
blank=True,
null=True,
verbose_name='Publishing date')
pages = models.IntegerField(
validators=[check_if_value_is_negative],
blank=True,
null=True,
verbose_name='Number of pages')
language = models.CharField(
max_length=2,
blank=True,
null=True,
verbose_name='Language')
And this is how my form looks before validation:
<tr><th><label for="id_title">Book title:</label></th><td><input type="text" name="title" value="Harry Potter i Kamień F
ilozoficzny" maxlength="75" required id="id_title"></td></tr>
<tr><th><label for="id_published_date">Publishing date:</label></th><td><input type="text" name="published_date" maxleng
th="10" id="id_published_date"></td></tr>
<tr><th><label for="id_pages">Number of pages:</label></th><td><input type="number" name="pages" id="id_pages"></td></tr
>
<tr><th><label for="id_language">Language:</label></th><td><input type="text" name="language" maxlength="2" id="id_langu
age"></td></tr>
Basically I have 2 forms. form 2 is used to input value which is used as argument in my search function. then this search function return .json, then i took some value from this .json and assign to "title" then this title is my initial data for form. And everything works fine until part with validation. My form isn't valid but when I print my form before validation part I see that my initial data is in form as expected.

Django by default requires Your fields in the form to be filled out. So BookForm requires that you have title, published_date, pages, and language filled out in the form. You instantiate form without passing actual input. Yes you pass in the initial, but you don't pass in request.POST to it (according to what's here). So your form instantiation should look like
form = self.form_class({'title': title, 'published_date': book.published_date, 'pages': book.pages, 'language': book.language})

Related

Django edit form won't validate on form.save() with user field

I recently added a "user" field to my Game model. I can create a new game that works fine; it's when I want to allow a user to edit the instance of a game where I am running into problems. My view is calling the form = GameForm(request.POST, instance=game) where game = Game.objects.get(pk=id). The form is pre-populated with the correct data, but when it's submitted, whether there are updates or not, the form is not validating. It sees it as a POST, but cannot get inside the if form.is_valid() conditional. And this is ever since I added the user field. I am using the default Django User model, and the field name is called "owner." It is set up as a ManyToManyField(User, blank=True) as users can own many games, and games can be owned by many users. Django forms the Many-To-Many "through" table, but I don't want the user to be able to change who owns what. I have it as a hidden field in my forms.py so a user can't change it.
Model
class Game(models.Model):
game_title = models.CharField(max_length=100,
verbose_name='Game Title',
db_column='game',
blank=False,
null=False,
unique=True)
game_developer = models.CharField(max_length=100,
verbose_name='Developer',
db_column='developer',
blank=True,
null=True)
game_release = models.DateField(max_length=50,
verbose_name='Release Date',
db_column='release_date',
blank=False,
null=True)
rating = models.IntegerField(verbose_name='Game Rating',
db_column='rating',
choices=INT_CHOICES,
blank=True,
null=True)
game_genre = models.CharField(max_length=100,
verbose_name='Genre',
db_column='genre',
blank=False,
null=True,
choices=GENRE_CHOICES)
game_platform = models.CharField(max_length=100,
verbose_name='Game Platform',
db_column='platform',
blank=True,
choices=PLATFORM_CHOICES)
game_esrb = models.CharField(max_length=100,
verbose_name='ESRB Rating',
db_column='esrb',
blank=False,
null=True)
owner = models.ManyToManyField(User, blank=True)
objects = models.Manager()
def __str__(self):
return self.game_title
class Meta:
db_table = 'tbl_games'
verbose_name = 'Game'
View
# Allows the user to update game information
def editGame(request, id):
# Finds the user selected game by game id
game = Game.objects.get(pk=id)
user = request.user.id
if request.method == 'POST':
print("Seen as POST")
# Create game instance pre-populated into a form
form = GameForm(request.POST, instance=game)
if form.is_valid():
print("Form is valid!")
# Saves the edits without saving to the dB
form.save()
messages.success(request, 'Game successfully updated!')
return redirect('library')
else:
print('Seen as GET')
form = GameForm(instance=game)
print("Page loaded")
context = {'form': form}
return render(request, 'library/editGame.html', context)
Form
class GameForm(ModelForm):
game_esrb = forms.CharField(required=False, widget=HiddenInput)
owner = forms.HiddenInput()
def __init__(self, *args, **kwargs):
super(GameForm, self).__init__(*args, **kwargs)
class Meta:
model = Game
fields = [
'game_title', 'game_developer', 'rating', 'game_release',
'game_genre', 'game_platform', 'game_esrb', 'owner'
]
widgets = {
'rating': Select(attrs={'choices': INT_CHOICES}),
'game_genre': Select(attrs={'choices': GENRE_CHOICES}),
'game_platform': Select(attrs={'choices': PLATFORM_CHOICES}),
'esrb_rating': HiddenInput(),
'owner': HiddenInput
}
help_texts = {
'rating':
'Key: 1 - Bad | 2 - Okay | 3 - Average | 4 - Good | 5 - Great'
}
Template
{% block appcontent %}
<div class="height">
<form method="POST">
{% csrf_token %}
{{ form|crispy }}
<div class="form-btn">
<a class=" btn btn-secondary cancel" type="button" href="{% url 'library' %}">Cancel</a>
<button class="update btn btn-primary" type="submit">Update</button>
</div>
</form>
</div>
{% endblock %}
First, remove 'owner' from the list assigned to fields in your GameForm class:
class Meta:
model = Game
fields = [
'game_title', 'game_developer', 'rating', 'game_release',
'game_genre', 'game_platform', 'game_esrb', # 'owner' removed
]
Here's why: inside your ModelForm, you only need to name the Game fields that you want to use as input fields in the form. In your case, you don't actually want the 'owner' field to be an input field, so just leave it out of this list. This will accomplish your stated goal of disallowing a user from modifying the owner field.
Now let's look at, and modify, your view:
You do this in your view: "user = request.user.id" This will assign an int to your attribute 'user'. I'm guessing you wanted a user object, not an int, but it's not clear, since you never actually use this attribute anywhere in your view.
Here's what needs to happen: Your view needs to connect the user object to the game object, then save the game object. We've already made sure that 'owner' doesn't appear as an input field in your form. Now, we need to manually provide a value for that field, then save the object. Below, we do this following the is_valid() call in your view:
def editGame(request, id):
# Finds the user selected game by game id
game = Game.objects.get(pk=id)
if request.method == 'POST':
print("Seen as POST")
# Create game instance pre-populated into a form
form = GameForm(request.POST, instance=game)
if form.is_valid():
print("Form is valid!")
form.save()
game.owner.add(request.user) # update the game's owner field, assigning the current user
game.save()
messages.success(request, 'Game successfully updated!')
return redirect('library')
To summarize and clarify: The end result of all this work is an updated Game object. The user updates values via the ModelForm. The view validates those changes, then saves the form, which saves the corresponding game object. Then, we manually assign the user to the 'owner' field of the game, because we intentionally left that field out of the form. Then we save the game again so this change is included in the 'final' state of the object.

How can I populate a manytomanyfield , using view, without django pre-built forms?

I'm building a storage web system using django, I'm very newbie on the framework, so the problem is that, there is a business rule, which demands, two kinds of products, the inside products, and the finished ones. And the finished ones, always are composed by one or more inside products, I have the idea of using the manytomanyfields, but now, I don't really know how to extract this data , that should be a multiple choice, from the form and save in the database, does anyone has any tips or better ideas?
Models.py
class Produto(models.Model):
codigo = models.CharField(max_length=254, null=True)
produto_desc = models.CharField(max_length=200, null=False)
tipo = models.CharField(max_length=2)
qtd = models.IntegerField(null=True, default=0)
created = models.DateTimeField(default=timezone.now, editable=False)
last_updated = models.DateTimeField(default=timezone.now, editable=False)
#Relationship Fields
estrutura = models.ManyToManyField(
'storage.Produto',
related_name="produto"
)
def __str__(self):
return self.produto_desc
Views.py
def CadastroProd(request):
temp = 0
lista_produto = Produto.objects.order_by('id')[:20]
for i in lista_produto:
temp += 1
if request.method == 'POST':
form = NovoProduto(request.POST)
if form.is_valid():
obj = Produto()
obj.save(commit=False)
obj.codigo = form.cleaned_data['codigo']
obj.produto_desc = form.cleaned_data['produto_desc']
obj.tipo = form.cleaned_data['tipo']
# obj.estrutura = form.cleaned_data['estrutura']
obj.save()
return HttpResponseRedirect('/storage/produtos')
lista_produto = Produto.objects.order_by('id')[:20]
lista_pi = Produto.objects.filter(tipo='PI')
lista_pa = Produto.objects.filter(tipo='PA')
context = {'lista_produto': lista_produto,
'temp': temp,
'lista_pi': lista_pi, 'lista_pa': lista_pa,
}
return render(request, 'storage/cadproduto/cadproduto.html', context)
forms.py
class NovoProduto(forms.Form):
codigo = forms.CharField(label='codigo', max_length=254)
produto_desc = forms.CharField(label='produto_desc', max_length=100)
tipo = forms.CharField(label='tipo', max_length=2)
estrutura = forms.IntegerField()
index
<div class="row">
<div class="col-md-3 mb-3">
<label for="pi-ida">Composição de Produtos Internos</label>
<select name="estrutura" multiple id="id_estrutura" required>
{%for prod in lista_pi%}
<option value="{{prod.id}}">{{prod.produto_desc}}</option>
{% endfor %}
</select>
</div>
</div>
<hr class="mb-4">
<button class="btn btn-primary btn-lg btn-block" type="submit">Cadastrar</button>
I expected that I can get information of the product, adn of the products that compose it
You are using your own custom template so get the selected choices list using the getlist() method and use set() method to save the manytomany field like this
Also if form is valid you need to save the form
if request.method == 'POST':
form = NovoProduto(request.POST)
estrutura = request.POST.getlist('estrutura')
if form.is_valid():
obj=form.save(commit=False)
obj.codigo = form.cleaned_data['codigo']
obj.produto_desc = form.cleaned_data['produto_desc']
obj.tipo = form.cleaned_data['tipo']
# obj.estrutura = form.cleaned_data['estrutura']
obj.save()
obj.estrutura.set(estrutura)
return redirect....
In your Form subclass, use ModelMultipleChoiceField:
class NovoProduto(forms.Form):
codigo = forms.CharField(label='codigo', max_length=254)
produto_desc = forms.CharField(label='produto_desc', max_length=100)
tipo = forms.CharField(label='tipo', max_length=2)
estrutura = forms.ModelMultipleChoiceField(queryset=Produto.objects.order_by('id')[:20])
The M2M field seems to be a self reference (unless you have two Models called Produto), so you might want to exclude the current product from the list of select options.
You can modify the queryset of a ModelMultipleChoiceField in the form's constructor:
def __init__(*args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance and self.instance.id:
self.fields['estrutura'].queryset = Produto.objects.exclude(id=self.instance.id).order_by('id')[:20]

Django forms extra empty radio button

In rendering a model form an extra radio button is produced and I don't know where it's coming from:
>>> f = DocumentForm()
>>> print f['document_type']
<ul id="id_document_type">
<li><label for="id_document_type_0"><input checked="checked" id="id_document_type_0" name="document_type" type="radio" value="" /> ---------</label></li>
<li><label for="id_document_type_1"><input id="id_document_type_1" name="document_type" type="radio" value="1" /> Campus LAN</label></li>
<li><label for="id_document_type_2"><input id="id_document_type_2" name="document_type" type="radio" value="2" /> WAN</label></li>
<li><label for="id_document_type_3"><input id="id_document_type_3" name="document_type" type="radio" value="3" /> UC</label></li>
</ul>
That first radio button with value="" and the text as ---------, I've scoured my code and can't work out where it originates from?
models.py
class DocumentType(models.Model):
name = models.CharField("Document Type", max_length=240)
class Document(models.Model):
document_type = models.ForeignKey(DocumentType,
verbose_name="Document Type")
>>> DocumentType.objects.all()
[<DocumentType: Campus LAN>, <DocumentType: WAN>, <DocumentType: UC>]
>>> d = Document.objects.all()
>>> for x in d:
... print x.document_type
...
Campus LAN
Campus LAN
template:
<form role="form" action="" method="POST">{% csrf_token %}
{{ form.as_p}}
<input type="submit" value="Save" />
</form>
forms.py:
class DocumentForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DocumentForm, self).__init__(*args, **kwargs)
self.fields['sections'].queryset = Section.objects.filter(associated_document="Original Section")
self.fields['document_type'].queryset = DocumentType.objects.all()
class Meta:
model = Document
fields = ('customer', 'title', 'document_type', 'sections',)
widgets = {
'sections': forms.widgets.CheckboxSelectMultiple,
'document_type': forms.widgets.RadioSelect,
}
views.py
def new_lld(request):
if request.method == "POST":
form = DocumentForm(request.POST)
if form.is_valid():
document = form.save(commit=False)
document.author = request.user
document.save()
form.save_m2m()
return redirect('lld:index')
else:
form = DocumentForm()
return render(request, 'lld/new_lld.html', {'form': form})
admin.py
class DocumentAdmin(admin.ModelAdmin):
fieldsets = [
('Document Info', {'fields': ['author', 'customer', 'title',
'slug']}),
('Document Type', {'fields': ['document_type', 'sections']}),
]
inlines = [VersionInline]
prepopulated_fields = {"slug": ("customer", "title",)}
list_display = ('title', 'customer', 'author', 'document_type',
'date_created', 'date_updated')
list_filter = ['date_updated', 'author']
Here we go:
https://docs.djangoproject.com/en/dev/ref/forms/fields/#django.forms.ModelChoiceField.empty_label
from here:
Django CheckboxSelectMultiple widget adds --------- value to query set
therefore:
self.fields['document_type'].empty_label = None
does the trick.
A work around is to hide it with css:
#id_document_type li:first-child {display:none}
As Agustin mentioned, ModelChoiceFields must be set to required in order to remove the blank choice.
def __init__(self, queryset, empty_label="---------",
required=True, widget=None, label=None, initial=None,
help_text='', to_field_name=None, limit_choices_to=None,
*args, **kwargs):
if required and (initial is not None):
self.empty_label = None
else:
self.empty_label = empty_label
Required is set to False by default, so you'll need to add the following to your init in Document Form
self.fields['document_type'].required=True
Django has to have a way to allow None values to be set for nullable fields (fields with required=False) and does so by appending an option with an empty value. The same thing happens with Select elements.
Now, for Django to add that option to your Form the document_type field must be nullable (indeed have required=False), and I can only assume that somewhere in the definition of the Form you're setting that option to the field.
PS: If the form is generated automatically for the Model (i.e. you're using Django's ModelForm) then the model should have said Field set with blank=True, null=True, yet that is clearly missing. ModelForm rocks, though, so if you're not familiar with it, try it out.
UPDATE:
TBH I can't work out why that's nullable either, but try setting required=True manually in the form in the same way that #Alistair specified.
self.fields['document_type'].required = True
Right under the line where you modified that field to set the queryset. I think that should work.
I solved this by adding these parameters to my declaration of my field in my model:
blank=False, default=None
So in this case, you model would look like this:
document_type = models.ForeignKey(DocumentType,
verbose_name="Document Type", blank=False, default=None)

Filter ForeignKey id From Django Models

I want to get data from some fields in a Foriegnkey that belongs to a particular id. But I can;t figure out how to make it work. I have two models;
class Tick(models.Model):
user=models.ForeignKey(User)
event_name=models.CharField(max_length=100)
ticket_quantity=models.PositiveIntegerField(max_length=50, null=True, blank=True,help_text='Optional, if you have unlimited ticket')
ticket_plan_name_a=models.CharField(max_length=100, null=True, blank=True)
ticket_plan_price_a=models.PositiveIntegerField(max_length=50, null=True, blank=True, verbose_name="Price",help_text='ticket price.')
ticket_plan_name_b=models.CharField(max_length=100, null=True, blank=True)
ticket_plan_price_b=models.PositiveIntegerField(max_length=50, null=True, blank=True, verbose_name="Price1",help_text='ticket price.')
Another Models
class BuyTick(models.Model):
user=models.ForeignKey(User)
tik=models.ForeignKey(Tick)
tiket_qty=models.PositiveIntegerField(max_length=100)
pub_date=models.DateTimeField()
full_name=models.CharField(max_length=100)
def __unicode__(self):
return self.tiket
class BuyTickForm(forms.ModelForm):
tik=forms.ModelChoiceField(queryset=Tick.objects.get(pk=tick_id))
class Meta:
model=BuyTick
After trying the above codes out I got
NameError: name 'tick_id' not defined
How can I get a particular tick_id from models Tick so as to display certain fields in the BuyTick form?
Update: For the views
def purchase_ticket(request, tikid):
if request.method=="POST":
form=BuyTickForm(request.POST)
if form.is_valid():
data=form.cleaned_data
newbuy=BuyTick(
user=request.user,
tik=Ticket.objects.get(pk=tikid),
tiket_qty=data['tiket_qty'],
full_name=data['full_name'],
phone_no=data['phone_no'],
pub_date=datetime.datetime.now())
newbuy.save()
return HttpResponse('Your ticket have been booked')
else:
print form.errors
else:
return render_to_response('buytickform.html',{'BuyTickForm':BuyTickForm,'post':Tick.objects.all().get(id=tikid)},context_instance=RequestContext(request))
Template
<p> Event Name: {{post.event_name}} </p>
<form action="." method="POST">
{% csrf_token %}
{{BuyTickForm.as_p}}
<input type="submit" class="radbutton radorange" value="Confirm Ticket"/>
</form>
First your model could use a couple of tweaks:
class BuyTick(models.Model):
. . .
tiket_qty=models.PositiveIntegerField(default=0, max_length=100)
pub_date=models.DateTimeField(auto_now_add=True)
. . .
def __unicode__(self):
# not sure what you're returning here...
return self.tiket
You need to pass in the id to filter on when you instantiate the form, and you can also pass in the user from the request, which you can set automatically:
class BuyTickForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.tikid = kwargs.pop('tikid')
self.user = kwargs.pop('user')
super(BuyTickForm, self).__init__(*args, **kwargs)
tik=forms.ModelChoiceField(queryset=Tick.objects.filter(id=self.tikid))
class Meta:
model=BuyTick
def save(self, commit=True):
buy_tick = super(BuyTickForm, self).save(commit=False)
buy_tick.user = self.user
if commit:
buy_tick.save()
return buy_tick
You need to filter() the Tick objects instead of using get, otherwise you'll be returning more than one object, which will raise an exception when using .get().
The view could use some love:
def purchase_ticket(request, tikid):
tick = get_object_or_404(Tick, id=tikid)
form = BuyTickForm(request.POST or None, tikid=tikid, user=request.user)
if request.method == 'POST' and form.is_valid():
form.save()
# note that these fields don't exist on your model:
# full_name, phone_no
# return something here... a redirect probably
else:
return render(request, 'buytickform.html',
{'tick': tick, 'form': form})

Let user add field to Django form

I am building a form that allows employees to enter the city, state of trip legs and calculate their mileage reimbursement automatically. Right now I have the following code in forms.py:
leg1a = forms.CharField(max_length=20, required=False)
leg1b = forms.CharField(max_length=20, required=False)
leg2a = forms.CharField(max_length=20, required=False)
leg2b = forms.CharField(max_length=20, required=False)
leg3a = forms.CharField(max_length=20, required=False)
leg3b = forms.CharField(max_length=20, required=False)
leg4a = forms.CharField(max_length=20, required=False)
leg4b = forms.CharField(max_length=20, required=False)
leg5a = forms.CharField(max_length=20, required=False)
leg5b = forms.CharField(max_length=20, required=False)
leg6a = forms.CharField(max_length=20, required=False)
leg6b = forms.CharField(max_length=20, required=False)
I then use a separate script to tally the miles in each leg and multiply the total by our mileage rate. It works, but it is ugly on the form; The form has six pairs of fields that are all optional. I would like to have 1 pair to start, and an option the user can click to add another leg.
I need to keep the data in tuple form, like so: ('city, ST','city2, ST') for the distance calculation function.
Here is the code I currently use in my form views for cleaning and sending the data to the mileage handler:
Mileage distance calculation
leg_list = [(leg1a, leg1b), (leg2a, leg2b), (leg3a, leg3b), (leg4a, leg4b), (leg5a, leg5b), (leg6a, leg6b)]
cleaned_leg_list = []
#get rid of empty variables
for leg in leg_list:
if leg == ('',''):
pass
else:
cleaned_leg_list.append(leg)
leg_distance = []
#sends each leg of mileage claim to distance handler
for leg in cleaned_leg_list:
pre_leg = main(leg)
leg_distance.append(pre_leg)
#sums legs of mileage claim
sum_distance = sum(leg_distance)
Use one form and a formset. A formset handles dealing with multiple instances of one form.
https://docs.djangoproject.com/en/dev/topics/forms/formsets/
Optionally, you can look into cloning formsets via javascript to dynamically add as many areas as you want without reloading the view.
This is also a great opportunity to clean up your code - put a method on the form definition that calculates leg distance and calls your external script. Your views can do the summation in a few lines of code!
You can even build the formset class yourself and add the summation function there, so your view is as simple as: if formset.is_valid(): print formset.sum_leg_distances()
class MyForm(forms.Form):
start_city = forms.CharField()
start_state = forms.CharField()
end_city = forms.CharField()
end_state = forms.CharField()
MyFormSet = formset_factory(form=MyForm, extra=6)
def my_view(request):
formset = MyFormSet(request.POST or None)
if request.POST:
if formset.is_valid():
for form in formset.forms:
form.cleaned_data['start_city'] # here's your leg data.
# form.calculate_leg_distance()
# sum([form.calculat_leg_distance() for form in formset])
return render(request, 'my_template', {'formset': formset})
<form method="post">
{{ formset.as_p }}
{{ formset.management_form }}
<input type="submit" />
</form>

Categories