How to retrieve user selections from a previous form - django

I have several forms that take people through steps and below are the first two and the simplest ones and makes it easy to explain what i am having problem with.
The following two views are login required and contain one form on each. First view is the new_operator where the user fills out a single text input field. Second view is the new_asset where the user fills one text input field as the asset name and selects an operator from the a select/dropdown field. The question is how can i get the form to remember the operator name the user created in the previous form and make it as the default option? To be clear, i still want the user to select any other operator if they choose to do so but i want the option they just created to be the default. Thanks a lot in advance for the help.
First, here are the models:
class OperatorCompany(models.Model):
name = models.CharField(max_length=50, unique=True)
created_at = models.DateTimeField(default=timezone.now)
created_by = models.ForeignKey(User, related_name='operator_added_by', null=True, on_delete=models.SET_NULL)
class Meta:
verbose_name = "Operator Company"
verbose_name_plural = "Operator Companies"
def __str__(self):
return self.name
class AssetName(models.Model):
name = models.CharField(max_length=50, unique=True)
operator = models.ForeignKey(OperatorCompany, related_name='asset', on_delete=models.CASCADE)
created_at = models.DateTimeField(default=timezone.now)
created_by = models.ForeignKey(User, related_name='asset_added_by', null=True,
on_delete=models.SET_NULL)
class Meta:
verbose_name = "Asset"
verbose_name_plural = "Assets"
def __str__(self):
return self.name
views.py
def new_operator(request):
if request.method == 'POST':
form = NewOperatorForm(request.POST)
if form.is_valid():
newoperator = form.save(commit=False)
newoperator.created_by = request.user
newoperator.created_at = timezone.now()
newoperator.save()
return redirect('wellsurfer:new_asset')
else:
form = NewOperatorForm()
return render(request, 'wellsurfer/create_new_operator.html', {'create_operator': form})
def new_asset(request):
if request.method == 'POST':
form = NewAssetForm(request.POST)
if form.is_valid():
newasset = form.save(commit=False)
newasset.created_by = request.user
newasset.created_at = timezone.now()
newasset.save()
return redirect('wellsurfer:new_pad')
else:
form = NewAssetForm()
return render(request, 'wellsurfer/create_new_asset.html', {'create_asset': form})
and following are the forms.py without the init, clean functions and the widgets
class NewOperatorForm(forms.ModelForm):
class Meta:
model = OperatorCompany
fields = ('name',)
class NewAssetForm(forms.ModelForm):
class Meta:
model = AssetName
fields = ('name', 'operator')

To share data between multiple pages, you can use session variables. These are stored on the server and associated to clients according to the session cookie they communicate to the server at every request.
Typically, in the first view, you would add after save():
request.session['latest_created_operator_id'] = newoperator.id
to save in the session the operator id.
And in the second view, after the else,
operator_id = request.session.get('latest_created_operator_id', None)
operator = Operator.objects.filter(id=operator_id).first() # returns None if not found
form = NewAssetForm(initial={'operator': operator})
retrieves the operator and populates the form.
(That's untested code; you may need to edit a bit.)

At a glance, maybe something like this would work.
What you can do is add another URL in urls.py for new_asset which accepts a OperatorCompany id. I don't have your url config but it could be something like:
urls.py
path('wellsurfer/new_asset/<int:operator_id>', new_asset, name='wellsurfer:new_asset_operator')
view.py
def new_operator(request):
if request.method == 'POST':
form = NewOperatorForm(request.POST)
if form.is_valid():
newoperator = form.save(commit=False)
newoperator.created_by = request.user
newoperator.created_at = timezone.now()
newoperator.save()
return redirect('wellsurfer:new_asset', operator_id=newoperator.id)
else:
form = NewOperatorForm()
return render(request, 'wellsurfer/create_new_operator.html', {'create_operator': form})
def new_asset(request, operator_id=None):
if request.method == 'POST':
form = NewAssetForm(request.POST)
if form.is_valid():
newasset = form.save(commit=False)
newasset.created_by = request.user
newasset.created_at = timezone.now()
newasset.save()
return redirect('wellsurfer:new_pad')
else:
form = NewAssetForm()
if operator_id is not None:
operator_company = OperatorCompany.objects.get(pk=operator_id)
form.fields['operator'].initial = operator_company
return render(request, 'wellsurfer/create_new_asset.html', {'create_asset': form})

Related

django passing id of the first saved form to the second form

My model has one to many relationships, when the user saves the first forms he will navigate to another form and the user should be able to fill up the second form and save it. Because it is a one-to-many relationship I want to pass the id instance of the first saved entry to the second form. However, when I fill up the second form and try to save it the data for the second form is not saved. I am not sure what I am doing wrong causes the data on the second form not to be saved.
view.py that saves the first forms
ef add_bank_form(request):
if request.method == 'POST':
form = BankForm(request.POST)
natural_person_form = NaturalPpersonPform(request.POST, request.FILES)
OwnShares = OwnSharesInOtherCompanyForm(request.POST, request.FILES)
if form.is_valid() and natural_person_form.is_valid():
form = form.save()
natural_person = natural_person_form.save(commit=False)
natural_person.domesticbank = form
natural_person.save()
share = OwnShares.save(commit=False)
share.domesticbank = form
share.save()
return HttpResponseRedirect('/add_source_of_income')
else:
form = BankForm()
natural_person_form = NaturalPpersonPform()
OwnShares = OwnSharesInOtherCompanyForm()
return render(request, 'add_domestic_bank.html',
{'form': form, 'natural_person_form': natural_person_form, 'OwnShares': OwnShares})
view.py that saves the second form
def add_source_of_income(request):
if request.method == 'POST':
get_bank_id = DomesticBank.objects.order_by('-pk')[0]
form = SourceOfIncomeForm(request.POST, request.FILES, instance=get_bank_id)
if form.is_valid():
form.save()
return HttpResponseRedirect('/DisplayBankRisk')
else:
form = SourceOfIncomeForm()
return render(request, 'add_income_source.html',
{'form': form})
class DomesticBank(models.Model):
bank_name = models.CharField(max_length=100, null=True)
subsidiary_or_branch = models.CharField(max_length=100, null=True)
home_country = models.TextField(max_length=500, null=True)
publish_date = models.DateField('Date Published', null=True)
def __str__(self):
return self.bank_name
class NaturalPerson(models.Model):
domesticbank = models.ForeignKey(DomesticBank, on_delete=models.CASCADE)
natural_person_name = models.CharField(max_length=100, null=True)
country = models.CharField(max_length=100, null=True)
upload_passport_copy = models.FileField(upload_to='Passport/PDF', null=True)
def __str__(self):
return self.natural_person_name
class OwnSharesInOtherCompany(models.Model):
domesticbank = models.ForeignKey(DomesticBank, on_delete=models.CASCADE)
name_of_companies = models.TextField(max_length=100, null=True)
business_type = models.CharField(max_length=100, null=True)
country = models.TextField(max_length=100, null=True)
upload_legal_documents = models.FileField(upload_to='LegalDocument/PDF', null=True)
You're initiating the wrong instance in your second form here get_bank_id = DomesticBank.objects.order_by('-pk')[0]. You are using a DomesticBank instance instead of a NaturalPerson instance.
Since you have a one-to-many relationship between banks and persons, you cannot use the bank id to identify a particular person (there could be several persons linked to the same bank).
Instead you can pass the person ID as an argument to your second view.
Views.py
def add_bank_form(request):
if request.method == 'POST':
form = BankForm(request.POST)
natural_person_form = NaturalPpersonPform(request.POST, request.FILES)
OwnShares = OwnSharesInOtherCompanyForm(request.POST, request.FILES)
if form.is_valid() and natural_person_form.is_valid():
form = form.save()
natural_person = natural_person_form.save(commit=False)
natural_person.domesticbank = form
natural_person.save()
share = OwnShares.save(commit=False)
share.domesticbank = form
share.save()
return HttpResponseRedirect(reverse('income_source', kwargs={'person_id': natural_person.id})
else:
form = BankForm()
natural_person_form = NaturalPpersonPform()
OwnShares = OwnSharesInOtherCompanyForm()
return render(request, 'add_domestic_bank.html',
{'form': form, 'natural_person_form': natural_person_form, 'OwnShares': OwnShares})
def add_source_of_income(request, person_id):
if request.method == 'POST':
person = NaturalPerson.objects.get(id=person_id)
form = SourceOfIncomeForm(request.POST, request.FILES, instance=person)
if form.is_valid():
form.save()
return HttpResponseRedirect('/DisplayBankRisk')
else:
form = SourceOfIncomeForm()
return render(request, 'add_income_source.html', {'form': form})
urls.py
#change the path for your second view to include the parameter:
path(/add_source_of_income/<int:person_id>', views.add_source_of_income, name='income_source'),

With Django, how do I reference an existing model in a form save method instead of creating a new instance?

I'm trying to use a ModelChoiceField to display options populated from model, and when a user selects a choice, store that method in a different model.
I'm using a standard form instead of a ModelForm, because I wasn't able to get the form to display how I wanted to when using a Modelform.
My issue is that in my form save method, a new instance is created, which is not what I want.
Here are the relevant models:
class Client(models.Model):
client_email = models.EmailField(max_length = 254)
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
phone = PhoneField(blank=True)
assigned_manager = models.ForeignKey(Manager, on_delete=models.CASCADE, blank=True, null=True)
created_date = models.DateTimeField(default=timezone.now)
#property
def full_name(self):
return '{0} {1}'.format(self.first_name, self.last_name)
class Manager(models.Model):
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
manager_email = models.EmailField(max_length = 254)
username = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True)
#property
def full_name(self):
return '{0} {1}'.format(self.first_name, self.last_name)
My view:
def manageclient(request, urlid):
client = Client.objects.get(id=urlid)
form = AssignManagerForm()
if request.method == "POST":
form = AssignManagerForm(request.POST)
if form.is_valid():
form.save()
return render(request, 'mysite/manageclient.html', {})
else:
form = AssignManagerForm()
context = {
'client': client,
'urlid': urlid,
'form': form,
}
return render(request, 'mysite/manageclient.html', context)
And my forms.py
class AssignManagerForm(forms.Form):
full_name = forms.ModelChoiceField(queryset=Manager.objects.all())
def save(self):
data = self.cleaned_data
client = Client(assigned_manager=data['full_name'])
client.save()
What I need to do is pass the urlid in my view to my save method in my forms.py, but I am unsure how to do that. Even if i could do that, I'm not sure how to modify form save to use urlid to refer to a specific record and set only the assigned_manager record.
Additionally, while I want the meta field to be used to display the form, I know it isn't what should be being passed to the assigned_manager field. How would I pass a Manager of instance to establish the foreign key relationship?
edit: edited to correct queryset in forms.py as per comments
Here is a solution using a ModelForm, by using a ModelForm you no longer have to manually set attributes on save or provide initial values when updating an existing instance.
The field assigned_manager will still be named assigned_manager but it's label can be overridden to be whatever you want it to be by passing labels in the ModelForm.Meta
class AssignManagerForm(forms.ModelForm):
class Meta:
model = Client
fields = ['assigned_manager']
labels = {'assigned_manager': 'Full name'}
def manageclient(request, urlid):
client = Client.objects.get(id=urlid)
if request.method == "POST":
form = AssignManagerForm(request.POST, instance=client)
if form.is_valid():
client = form.save()
# The general convention is to redirect after a successful POST
else:
form = AssignManagerForm(instance=client)
context = {
'client': client,
'urlid': urlid,
'form': form,
}
return render(request, 'mysite/manageclient.html', context)
Instead of saving it in form, you can directly do this operation in view. For example:
def manageclient(request, urlid):
client = Client.objects.get(id=urlid)
if request.method == "POST":
form = AssignManagerForm(request.POST)
if form.is_valid():
client.assigned_manager = form.cleaned_data['full_name']
client.save()
return render(request, 'mysite/manageclient.html', {})

Use a different form based on variable

I have a "product" field that I want to use to determine which form to display. I am trying to do this in the view but wondering if I should do it in the template instead. I have tried the following but "form" does not get assigned by my if statements. What am I doing wrong?
#login_required
def update_message(request, pk):
message = get_object_or_404(Submission, pk=pk)
author = message.author
date_posted = message.date_posted
product = message.product
message_obj = Submission.objects.get(pk=pk)
program_type = message.program_type
if author == request.user:
if request.method == 'POST':
if product == 'Apple':
form = AppleForm(request.user, request.POST, instance=message)
if product == 'Orange':
form = OrangeForm(request.user, request.POST, instance=message)
if form.is_valid():
message_sub = form.save(commit=False)
message_sub.author = request.user
message_sub.date_posted = timezone.now()
message_sub.save()
form.save_m2m()
messages.success(request, 'Message updated')
return redirect('submission-list')
else:
if product == 'Apple':
form = AppleForm(request.user, instance=message)
if product == 'Orange':
form = OrangeForm(request.user, instance=message)
else:
messages.warning(request, 'You can't do that.')
return redirect('message-submission-list')
return render(request, 'programs/submission_create_form.html', {'product':product,'form': form, 'message_obj': message_obj,'program_type':program_type})
class MessageSubmission(models.Model):
message = models.CharField(max_length=5000)
author = models.ForeignKey(User, on_delete=models.CASCADE)
date_posted = models.DateTimeField(default=timezone.now)
program_code = models.ManyToManyField(Program)
program_type = models.CharField(max_length=30, blank=True)
product = models.ForeignKey('Product', on_delete=models.SET_NULL, null=True)
production_cycle = models.ManyToManyField('ProductionCycle', null=True)
def get_absolute_url(self):
return reverse('submission-list')
def __str__(self):
return self.message
As I mentioned in the comment, the issue is that product is a ForeignKey to another model. In the template, the FK will display using the __str__ method of that model, but that doesn't make it equal to that display value. You should compare explicitly with the relevant field on the target model:
if product.fruit_type == 'Orange' # or whatever the field is
(Alternatively you could do if str(product) == 'Orange' but that's more brittle and is coupling display logic in a way that's not very nice.)
There's nothing wrong with doing this in the views. If the form is not defined after those if statements then it means that the value of product is not Apple or Orange, but something else. I would double check the value of product to fix the issue.
Since Product is a class, you should reference a field. You didn't post the code for it, but for example
if form == product.name
If there is a name field.

Django forms: how to show only objects associated with user in dropdown

I am a vet hospital, with class Pet and class Records. Each pet can have many records, i.e. everytime it visits the hospital it gets a new record.
At the moment, my form shows all the pets ever associated with my app (please view https://i.stack.imgur.com/8j7V8.png).
I want only the user's registered pets to appear (why would Bob be bringing a stranger's cat to the vet?)
View to add a record:
#login_required(login_url="/accounts/login/")
def record_create(request):
#this line retrieves Pets only belonging to the user logged in
pets = Pet.objects.filter(author=request.user)
if request.method == 'POST':
form = forms.CreateRecord(request.POST, request.FILES)
print(form)
if form.is_valid():
instance = form.save(commit=False)
instance.author = request.user
instance.save()
return redirect('records')
else:
form = forms.CreateRecord()
return render(request, 'records/record_create.html', {'form': form,})
forms.py
class CreateRecord(forms.ModelForm):
class Meta:
model = models.Record
fields = ['feedID', 'amountLeftOver', 'amountDispensed', 'additionalInfo', 'selectPet']
models.py
class Pet(models.Model):
petName = models.CharField(max_length=100, default='My Pet')
petImage = models.ImageField(default='default.png', blank=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, default=None)
def __str__(self):
return self.petName
class Record(models.Model):
feedID = models.AutoField(primary_key=True)
dateTime = models.DateTimeField(auto_now_add=True)
amountLeftOver = models.IntegerField(default='0')
amountDispensed = models.IntegerField(default='0')
additionalInfo = models.TextField(default=" ")
selectPet = models.ForeignKey(Pet, on_delete=models.CASCADE)
author = models.ForeignKey(User, on_delete=models.CASCADE, default=None)
How do I get the selectPet dropdown to show only Bob's registered pets?
Thanks for your time!
You can override the __init__() method of your form to pass in extra arguments, in this case, you can pass in a user instance, and set the queryset for the dropdown widget.
(Not sure about how you have related the Pet model to the User model, the example below assumes you have a foreign key)
class CreateRecord(forms.ModelForm):
class Meta:
model = models.record
fields = ['feedID', 'amountLeftOver', 'amountDispensed', 'additionalInfo', 'selectPet']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
user = self.kwargs.get('user')
if user:
self.fields['selectPet'].queryset = user.pet_set.all()
Another way to resolve this problem
View to add a record:
#login_required(login_url="/accounts/login/")
def record_create(request):
#this line retrieves Pets only belonging to the user logged in
pets = Pet.objects.filter(author=request.user)
if request.method == 'POST':
form = forms.CreateRecord(request.POST, request.FILES)
print(form)
if form.is_valid():
instance = form.save(commit=False)
instance.author = request.user
instance.save()
return redirect('records')
else:
form = forms.CreateRecord()
form.fields["category"].queryset=Record.objects.filter(user=request.user)
return render(request, 'records/record_create.html', {'form': form,})

How to input a model instance in a form field

I have a simple model with 2 classes:
class Company(models.Model):
company_name = models.CharField(default='', max_length=128, blank=True, null=True)
class Visitor(models.Model):
visitor_company = models.ForeignKey(Company)
visitor_name = models.CharField(default='', max_length=128, blank=False, null=False)
I also have a simple form:
class VisitorForm(forms.ModelForm):
visitor_company = forms.CharField()
class Meta:
model = Visitor
fields = "__all__"
And here is the view.py code:
def home(request):
form = Visitor()
if request.method == "POST":
form = Visitor(request.POST)
if form.is_valid():
obj, created = Visitor.objects.get_or_create(**form.cleaned_data)
if created:
messages.add_message(request, messages.SUCCESS, 'Visitor added.')
else:
messages.add_message(request, messages.INFO, 'Visitor exists : %s' % obj.visitor_name)
return redirect('visitors')
context = { 'form': form }
return render(request, "visitors/home.html", context)
I have set visitor_company as a CharField as I want to use Typeahead for users to specify the ForeignKey, rather than Django's built in dropdown (which would appear if I did not set the input type).
However, when I use this method, even if I input a valid company_name in the visitor_company field, I get Cannot assign "XXX": "Visitor.visitor_company" must be a "Company" instance.
How do I input a Company instance? Is it also possible to use get_or_create on a ForeignKey like this if the Company record doesn't exist?
This is untested code, so consider this a starting point, no real solution:
forms.py
class VisitorForm(forms.ModelForm):
visitor_company = forms.CharField()
def clean_visitor_company(self):
vc = self.cleanded_data['visitor_company']
try:
vc_object = Company.objects.get(company_name=vc)
except Company.DoesNotExist:
vc_object = Company.objects.create(company_name=vc)
return vc_object
class Meta:
model = Visitor
fields = "__all__"
views.py
def home(request):
form = VisitorForm(request.POST or None)
if form.is_valid():
form.save()
return redirect('visitors')
return render(request, "visitors/home.html", { 'form': form })