I'm displaying two separate sample projects. The first is a Contact related and shows the principle of using the formwizard. The second is an ingredients to recipes related project which shows how to use inlines within a form. I want inlines to be in my formwizard the same way they work in a normal form.
I have a formwizard multistep form working. It is based off the example here. I've changed it slightly to use modelform.
models.py
from django.db import models
# Create your models here.
class Contact(models.Model):
subject = models.CharField(max_length=50)
sender = models.EmailField()
def __unicode__(self):
return self.subject
class Contact2(models.Model):
message = models.TextField(max_length=500)
def __unicode__(self):
return self.message
forms.py
class ContactForm1(forms.ModelForm):
class Meta:
model = Contact
class ContactForm2(forms.ModelForm):
class Meta:
model = Contact2
class ContactWizard(FormWizard):
#property
def __name__(self):
return self.__class__.__name__
def done(self, request, form_list):
# do_something_with_the_form_data(form_list)
return HttpResponseRedirect('/done/')
urls.py
(r'^contact/$', ContactWizard([ContactForm1, ContactForm2])),
Separately I have inlines being generated into another form. I'm doing this via inlineformset_factory in my view. This is not connected to the formwizard example above. This is an ingredients to recipes example.
I'm doing this like:
views.py
def add(request):
IngredientFormSet = inlineformset_factory(Recipe, Ingredient,
fk_name="recipe",
formfield_callback=curry(ingredient_form_callback, None))
if request.method == 'POST':
form = RecipeForm(request.POST)
formset = IngredientFormSet(request.POST)
if form.is_valid() and formset.is_valid():
recipe = form.save()
formset = IngredientFormSet(request.POST, instance=recipe)
formset.save()
return redirect("/edit/%s" % recipe.id)
else:
form = RecipeForm()
formset = IngredientFormSet()
return render_to_response("recipes_add.html", {"form":form, "formsets":formset}, context_instance=RequestContext(request))
recipes_add.html
<form method="post">
{% csrf_token %}
<table>
{{ form }}
</table>
<hr>
<h3>Ingredients</h3>
<div class="inline-group">
<div class="tabular inline-related last-related">
{{ formsets.management_form }}
{% for formset in formsets.forms %}
<table>
{{ formset }}
</table>
{% endfor %}
</div>
</div>
<p class="success tools">Add another row</p>
<input type="submit" value="Add">
</form>
How can I get the inlines to work within my formwizard multistep form?
The models.py now looks like this because I want books to be inlines to contact. I want the inlines to be on the first step of my formwizard. Then go through to step 2 and finish.
from django.db import models
# Create your models here.
class Contact(models.Model):
subject = models.CharField(max_length=50)
sender = models.EmailField()
def __unicode__(self):
return self.subject
class Contact2(models.Model):
message = models.TextField(max_length=500)
def __unicode__(self):
return self.message
class Book(models.Model):
author = models.ForeignKey(Contact)
title = models.CharField(max_length=100)
The formwizard included in Django (below version 1.4) doesn't support formsets. Beginning with version 1.4, there will be a much better implementation (see https://docs.djangoproject.com/en/dev/ref/contrib/formtools/form-wizard/)
Back to your question, if you can't wait for the next Django release - which I assume - you could stick to django-formwizard. The last release (1.0) is api compatible to the upcoming Django formwizard.
With the new formwizard implementation you can use FormSets the same way you use normal Forms.
Related
I created a form for adding products to an e-Commerce site. The form isn't working perfectly.
First issue: I want to store the user automatically by submitting the form. I actually want to store Who did add the product individually.
Second Issues: The image field is not working, the image is not stored in the database.
How can I fix these issues? help me
forms.py:
from django import forms
from .models import Products
from django.forms import ModelForm
class add_product_info(forms.ModelForm):
class Meta:
model = Products
fields = ('product_title','product_image')
model.py:
class Products(models.Model):
user = models.ForeignKey(User, related_name="merchandise_product_related_name", on_delete=models.CASCADE, blank=True, null=True)
product_title = models.CharField(blank=True, null=True, max_length = 250)
product_image = models.ImageField(blank=True, null=True, upload_to = "1_products_img")
views.py:
def add_product(request):
if request.method == "POST":
form = add_product_info(request.POST)
if form.is_valid():
form.save()
messages.success(request,"Successfully product added.")
return redirect("add_product")
form = add_product_info
context = {
"form":form
}
return render(request, "add_product.html", context)
templates:
<form action="" method="POST" class="needs-validation" style="font-size: 13px;" novalidate="" autocomplete="off" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<div class="d-flex align-items-center">
<button type="submit" class="btn btn-outline-dark ms-auto" style="font-size:13px;">Add</button>
</div>
</form>
You need to set the .user of the .instance wrapped in the form to the logged in user (request.user). Furthermore you need to pass both request.POST and request.FILES to the form to handle files.
from django.contrib.auth.decorators import login_required
#login_required
def add_product(request):
if request.method == 'POST':
form = add_product_info(request.POST, request.FILES)
if form.is_valid():
form.instance.user = request.user
form.save()
messages.success(request, 'Successfully product added.')
return redirect('add_product')
else:
form = add_product_info()
context = {
'form': form
}
return render(request, 'add_product.html', context)
I would also advise not to use null=True nor blank=True, unless a field is really optional. Likely the product_title should not be optional, nor should the user be, since you use CASCADE in case the user is removed.
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: Forms in Django are written in PascalCase, not snake_case,
so you might want to rename the model from add_product_info to ProductInfoForm.
Note: normally a Django model is given a singular name, so Product instead of Products.
why are you using the ForeignKey with your user. the first issue i notice is with the class Meta. Pass this as a list not tuple.
class add_product_info(forms.ModelForm):
class Meta:
model = Products
fields = [
'product_title',
'product_image',
]
then try this as well.
class Products(models.Model):
user = models.OneToOneField(User, related_name="merchandise_product_related_name", on_delete=models.CASCADE, blank=True, null=True)
I would like to be able to register different models from a single front view, as I can do from the admin create view.
for example in models.py:
class Book(models.Model):
"""Model representing a book (but not a specific copy of a book)."""
title = models.CharField(max_length=200)
author = models.ForeignKey('Author', on_delete=models.SET_NULL,
null=True)
in admin.py I have:
#admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
in the admin panel I get
I would like to be able to add author by the same way from the create book view in frontend, thank you for your help
For this make create modelform for Book Model in forms.py
class AddBook(forms.ModelForm):
class Meta:
model = Book
fields = ['title','author']
set url for adding the book in urls.py
path('add/book/',views.add_book,name='add_book')
In views.py
def add_book(request):
if request.method == POST:
form = AddBook(request.POST)
if form.is_valid():
book = form.save(commit=False)
book.save()
return redirect('redirect where you want to redirect')
else:
form = AddBook()
return render (request,'add_book.html',{'form':form})
add_book.html
<form action= '{% url 'add_book' %} method='post'>
{% csrf_token %}
{{ form.as_p }}
<button type = 'submit'>Submit</button>
</form>
OR you can you use the built-in class-based generic views https://docs.djangoproject.com/en/2.1/topics/class-based-views/generic-editing/
In my django form I am using a method to filter the drop down options to the ones that are related to the logged-in user. After the implementation the displayed values changed to objects rather than the __str__ value. I am posting the simplified codes and a snapshot that shows this. I have followed everything needed, but I cannot figure out why this is happening:
models.py
class Business(models.Model):
client=models.ForeignKey('Client',on_delete=models.CASCADE, limit_choices_to={'is_active':True},)
name=models.CharField(max_length=30,blank=False, unique=True,)
def __str__(self):
return self.name
class MMRequestAttributes(models.Model):
client=models.ForeignKey('Client',on_delete=models.CASCADE, limit_choices_to={'is_active':True},)
business=models.ForeignKey('Business', on_delete=models.CASCADE,limit_choices_to={'is_active':True},)
class Ticket(MMRequestAttributes):
no=models.CharField('Ticket Number',max_length=50,default=uuid.uuid4,null=False, blank=False, editable=False, unique=True)
subject=models.CharField('Subject',max_length=100,null=False, blank=False)
description=models.TextField('Description',max_length=500,null=True,blank=True)
created_at=models.DateTimeField('Created at',auto_now_add=True, editable=False)
updated_at=models.DateTimeField('Updated at',auto_now=True, editable=False)
created_by= models.ForeignKey(settings.AUTH_USER_MODEL)
status=StateField(editable=False)
def __str__(self):
return 'Ticket #' + str(self.pk)
views.py
def new_ticket(request):
form=NewTicket(request.user)
return render(request,'mmrapp/new_ticket.html',{'form':form})
admin.py
class UserExtend(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, blank=False,null=False,)
client=models.ForeignKey('Client', on_delete=models.CASCADE,limit_choices_to={'is_active': True},)
forms.py
from django import forms
from .models import Ticket, Business
from .admin import UserExtend
from django.forms import ModelChoiceField
class NewTicket(forms.ModelForm):
def __init__(self,user, *args, **kwargs):
super(NewTicket, self).__init__(*args, **kwargs)
try:
client_id = UserExtend.objects.values_list('client_id', flat=True).get(user=user)
self.fields['business'].queryset=Business.objects.filter(client__id=client_id)
except UserExtend.DoesNotExist:
### there is not userextend corresponding to this user, do what you want
pass
class Meta:
model=Ticket
fields = ('subject','business')
new-ticket.html
{% extends 'mmrapp/__l_single_column.html' %}
{% load static %}
{% block main_col %}
<h1>New Ticket</h1>
<form method="POST" class="new-ticket">{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-default">Submit</button>
</form>
{% endblock main_col %}
Finally I found the problem. Somehow django cannot load the original models' string representation of their objects (__str__) when showing them as choices in the drop downs. I had to explicitly define them again inside the from model. The attribute is called label_from_instance. The good thing is this way one can display them different from what they are originally defined in the models.
so the monkey patch would be:
self.fields['business'].label_from_instance = self.business_label
#staticmethod
def business_label(self):
return str(self.name)
In Python 2.7 we were using
def unicode(self):
return self.name
After the python upgrade from 2.7 to 3.6 it was showing all object references in the dropdown so I added
def str(self):
return self.name
I added this method to the model in question:
def __str__(self):
return self.whatever_prop_suits_your_needs
In my app, I have Users create Post objects. Each Post has a User
class Post(models.Model):
user = models.ForeignKey(User, on_delete = models.CASCADE)
...
I want to create a post-submission form for editing and submission, so I plan to use Django's ModelForm functionality.
class PostForm(ModelForm):
class Meta:
model = Post
fields = "__all__"
However, if I do this, then whoever is viewing the form will be able to set who the Post author is. I want to make sure that the resulting user field is them. But, if I exclude the user field from the ModelForm,
class PostForm(ModelForm):
class Meta:
model = Post
exclude = 'user'
then the user will not be set on form submission. I've hacked my way around this by making a custom form and updating the post field
def submit_view(request):
....
request.POST = request.POST.copy()
request.POST.update({
'user' : request.user.id
})
form = PostForm(request.POST, request.FILES)
....
but then I lose automatic UI generation and form validation, which in some ways defeats the purpose of the Form class. Could somebody point me to the idiomatic way of setting the user field without including it in the Form?
Try this view:
def submit_view(request):
form = PostForm(request.POST or None)
if form.is_valid():
new_post = form.save(commit=False)
new_post.user = request.user
new_post.save()
view.py
from django.views.generic import CreateView
from .models import Post
class PostCreate(CreateView):
model = Post
template_name ="new_Post_form.html"
fields = ['text']
def form_valid(self, form):
object = form.save(commit=False)
object.user = self.request.user
object.save()
return super(PostCreate, self).form_valid(form)
def get_success_url(self):
return "/"
url.py
url(r'^newpost$',views.PostCreate.as_view(),name='post_new',),
new_post_form.html
<form method="post" enctype="multipart/form-data" class="form" action="newpost" id="new-post-form">
<div class="modal-body">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</div>
I have a generic CreateView which displays the model and a related ForeignKey model as a form with inlines. Using a similar model with a similar related ForeignKey model as a source - how can i pre-fill the original form and get only the proper amount of inlines according to an object from the source?
The closest i got to a solution is using CreatWithInlinesView from django-extra-views which gives me an empty form with the related model as inlines. But how do i get the data from an existing object of another model into this form with the proper amount of inlines that the object needs?
models.py
class Offers(models.Model):
reference = models.CharField(unique=True, max_length=10)
user = models.ForeignKey(User)
…
class OfferDetail(models.Model):
offer = models.ForeignKey(Offers, related_name='offerdetails')
product_name = models.CharField(max_length=255)
…
# where the data for prefilling comes from:
class Orders(models.Model):
reference = models.CharField(unique=True, max_length=10)
…
class OrderDetail(models.Model):
order = models.ForeignKey(Orders, related_name=‘orderdetails')
product_name = models.CharField(max_length=255)
…
urls.py
url(r'^offer/(?P<reference>[A-Z]{9})/$', views.MyOfferView.as_view(), name=‘somename’),
now if a user visits url offer/REFERENCE he should see a form that creates an offer but is pre-filled from the order object that has the reference REFERENCE
without pre-filled data from the order object it is working like this (using django-extra-views)
views.py
from extra_views import CreateWithInlinesView, InlineFormSet
class DetailsInline(InlineFormSet):
model = OfferDetail
class MyOfferView(CreateWithInlinesView):
model = Offers
inlines = [DetailsInline, ]
template_name = ‘someapp/somename.html'
success_url = ‘/someurl’
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(MyOfferView, self).dispatch(*args, **kwargs)
somename.html
<form action="" method="post">
{% csrf_token %}
{{ form|crispy }}
{% for i in inlines %}
{{ i|crispy }}
{% endfor %}
<input type="submit" value="Submit" class="btn btn-primary"/>
</form>
You may find this documentation helpful:
https://docs.djangoproject.com/en/1.8/ref/forms/api/#dynamic-initial-values