Django - Pass Session Variables From One View To Another ('request' is undefined) - python

I have looked at this (django variable of one view to another from session), but I believe the desired outcome is quite different.
I have two views in my views.py file: projectcreation and projectconfirm.
After the user fills out a form in the projectcreation view, I want them to be directed to a confirmation page that gives a read-only view of the variables before proceeding with the project creation.
My views.py file looks like this:
from django.shortcuts import render
from django.http import HttpResponse
from .projectform import ProjectForm
from .projectconfirm import ProjectConfirm
def projectcreation(request):
if request.method == 'POST':
form = ProjectForm(request.POST)
if form.is_valid():
request.session['projectname'] = form.cleaned_data['client'] + "-" + form.cleaned_data['stage'] + "-" + form.cleaned_data['purpose']
request.session['computeapi'] = form.cleaned_data['computeapi']
request.session['deploymentmanapi'] = form.cleaned_data['deploymentmanapi']
request.session['storagecompapi'] = form.cleaned_data['storagecompapi']
request.session['monitorapi'] = form.cleaned_data['monitorapi']
request.session['loggingapi'] = form.cleaned_data['loggingapi']
return render(request,'projectconfirm.html')
else:
form = ProjectForm()
return render(request, 'projectform.html', {'form': form})
def projectconfirm(request):
if request.method =='POST':
print("Now beginning deployment...")
else:
form = ProjectConfirm()
return render(request, 'projectconfirm.html', {'form': form})
The problem I'm facing and admittedly not understanding is how to load the session variables in the projectconfirm.py script.
I thought something like the following would work, but it's complaining that 'request' is an undefined variable:
from django import forms
from django.shortcuts import render
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit, Row, Column, Field, Fieldset
class ProjectConfirm(forms.Form):
name = forms.CharField(widget=forms.TextInput(attrs={'placeholder': request.session['projectname']}))
computeapi = forms.CharField(widget=forms.TextInput(attrs={'placeholder': request.session['computeapi']}))
deploymentmanapi = forms.CharField(widget=forms.TextInput(attrs={'placeholder': request.session['deploymentmanapi']}))
storagecompapi = forms.CharField(widget=forms.TextInput(attrs={'placeholder': request.session['storagecompapi']}))
monitorapi = forms.CharField(widget=forms.TextInput(attrs={'placeholder': request.session['monitorapi']}))
loggingapi = forms.CharField(widget=forms.TextInput(attrs={'placeholder': request.session['loggingapi']}))
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(
'Project Name',
Row(
Column('name', css_class='form-group col-md-4 mb-0', readonly=True),
)
),
Fieldset(
'APIs To Enable',
Row(
Column('computeapi', css_class='form-group col-md-4 mb-0', readonly=True),
Column('deploymentmanapi', css_class='form-group col-md-4 mb-0', readonly=True),
Column('storagecompapi', css_class='form-group col-md-4 mb-0', readonly=True),
Column('monitorapi', css_class='form-group col-md-4 mb-0', readonly=True),
Column('loggingapi', css_class='form-group col-md-4 mb-0', readonly=True)
)
),
Submit('Deploy', 'Deploy', css_class='btn-success')
)

In constructor of Form request can be obtained by:
Passing it through **kwargs, so:
# in your view:
form = ProjectConfirm(request=request)
# in ProjectConfirm
class ProjectConfirm(forms.ModelForm):
name = forms.CharField(widget=forms.TextInput(attrs={}))
# etc
def __init__(self, *args, **kwargs):
request = kwargs.pop("request")
super().__init__(*args, **kwargs)
# ... and then define your widgets inside your __init__
self.fields['name'].widget.attrs['placeholder'] = request.session["projectname"]
# etc
By defining Form as a nested class of your view, but it has to be class-view instead of function.Then you can pass it to your form, still it's not an elegant solution as it's mixing views and forms in the same module. Anyway it will be something like that:
class YourView(FormView):
def get_form_class(self):
request = self.request
class ProjectConfirm(forms.Form):
# your existing form definition
return ProjectConfirm
Let me know if it's helpful for you.

Related

How to specify field choices in django inlineformset

I wish to only display a subset of the choices for a model form field. E.g Depending on the url the user is at I might want only 'weight gain' and 'parkinsonism' displayed as options for the 'se_name' field.
I can work out how to get the url as a parameters in the view (p = self.request.GET.get("p", None)) But I cant work out how to use this parameter to limit the choices available.
This is the formset
SideeffectFormSet = inlineformset_factory(
Case,
SideEffect,
fields=("se_name",),
widgets={'concern': RangeInput()},
extra=0,
min_num=1,
validate_min=True,
)
Which is based on the model:
class SideEffect(TimeStampedModel):
SE_CHOICES = [
("weight_gain", "Weight Gain"),
("parkinsonism", "Parkinsonism"),
("dystonia", "Dystonia"),
("none", "None"),
]
se_name = models.CharField("",max_length=200, choices=SE_CHOICES, default="none")
case = models.ForeignKey(Case, on_delete=models.CASCADE)
And the form is rendered by this view:
class CaseView(LoginRequiredMixin, TemplateView):
model = Case
template_name = "se_balance/se_balance.html"
def get(self, *args, **kwargs):
p = self.request.GET.get("p", None)
sideeffect_formset = SideeffectFormSet(queryset=SideEffect.objects.none(),)
return self.render_to_response(
{ "sideeffect_formset": sideeffect_formset,
"sideeffect_formsethelper": SideEffectFormSetSetHelper,
}
)
To change the choices of a field in a formset dynamically you need to define a custom form class that does the choice alteration on __init__
from django import forms
from .models import SideEffect
class SideEffectForm(ModelForm):
class Meta:
model = SideEffect
fields = ['se_name']
def __init__(self, *args, p, **kwargs):
super().__init__(*args, **kwargs)
if p == 'foo':
self.fields['se_name'].choices = [
("weight_gain", "Weight Gain"),
("parkinsonism", "Parkinsonism"),
]
else:
...
Use the form class in your formset
SideeffectFormSet = inlineformset_factory(
Case,
SideEffect,
form=SideEffectForm,
extra=0,
min_num=1,
validate_min=True,
)
Then in your view you can pass custom parameters to the formset form
sideeffect_formset = SideeffectFormSet(queryset=SideEffect.objects.none(), form_kwargs={'p': p})

Form is invalid but no errors

Whenever I submit the form, it is invalid and there is no error message attached to it when I try to read it with form.errors; it's empty. Here is what I have:
models.py
class Project(models.Model):
project = models.CharField(unique=True, max_length=50)
is_active = models.BooleanField(default=False)
forms.py
from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Row, Column, Submit, Field
class SelectProjectForm(forms.Form):
def __init__(self, active_choices, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['is_active'] = forms.ChoiceField(choices=active_choices, widget=forms.Select)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.layout = Layout(
Row(
Column(Field('is_active'))
),
Row(
Column(FormActions(Submit('activate', 'Activate Project')))
),
)
views.py
class ProjectSettings(LoginRequiredMixin, TemplateView):
template_name = 'Home/project_settings.html'
def get(self, request, *args, **kwargs):
active_choices = []
for project in Project.objects.all():
active_choices.append((project.id, project.project),)
return render(request, self.template_name, {'form': SelectProjectForm(active_choices)})
def post(self, request, *args, **kwargs):
if 'activate' in request.POST:
form = SelectProjectForm(request.POST)
if form.is_valid():
....
messages.error(request, 'Something went wrong')
return redirect('project_settings')
project_settings.html:
<div>
{% load crispy_forms_tags %}
{% crispy form %}
</div>
I think the problem might be in the POST method in views where I initialize the form, but I don't know how to pass the active_choices parameter in post. If that is not the problem then I am lost.

One form for two models in django

Django==3.1.7
django-crispy-forms==1.11.2
I 've 2 models: Order and OrderList
Order is a header and OrderList is a tabular section of the related Order
class Order(models.Model):
print_number = models.PositiveIntegerField(
verbose_name=_("Number"),
default=get_todays_free_print_number,
)
# ... some other fields
class OrderList(models.Model):
order = models.ForeignKey(
Order,
blank=False,
null=False,
on_delete=models.CASCADE
)
item = models.ForeignKey(
Item,
verbose_name=_("item"),
blank=True,
null=True,
on_delete=models.CASCADE
)
# ... some other OrderList fields
The question is how to create a form containing both models and provide the ability to add an OrderList positions within an Order into the form
and save them both.
What I did:
forms.py - I used inline formset factory for the OrderList
from django.forms import ModelForm
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from .models import Order, OrderList
class OrderForm(ModelForm):
class Meta:
model = Order
fields = [
'__all__',
]
class OrderListForm(ModelForm):
class Meta:
model = OrderList
fields = [
'__all__',
]
class OrderListFormSetHelper(FormHelper):
"""Use class to display the formset as a table"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.template = 'bootstrap4/table_inline_formset.html'
# I am not sure we should add a button here
####################################################
self.add_input(Submit('submit', 'Submit',
css_class='btn btn-primary offset4'))
views.py
#login_required
def orders(request):
template = f'{APP_NAME}/index.html'
list_helper = OrderListFormSetHelper()
list_formset = inlineformset_factory(Order,
OrderList,
OrderListForm,)
if request.method == 'POST':
form = OrderForm(request.POST, prefix="header")
if form.is_valid() and list_formset.is_valid():
order = form.save()
order_list = list_formset.save(commit=False)
order_list.order = order
order_list.save()
return HttpResponseRedirect(reverse('order_created'))
else: # all other methods means we should create a blank form
form = OrderForm()
return render(request, template, {'form': form,
'list_form': list_formset,
'list_helper': list_helper})
index.html
<form method="post">
{% csrf_token %}
{% crispy form %}
{% crispy list_form list_helper %}
<!-- the button below doesn't make sense because it does nothing.
the self.add_input in forms.py already adds a submit button.
-->
<button type="submit" class="btn btn-primary">
{% translate "Send an order" %}
</button>
</form>
The resulting html renders the page like that:
But when I press the submit button
it clean up Order related fields and mark them as blank
You use the crispy template tag to render your forms. It uses the FormHelper class to help render your forms, which by default has the attribute form_tag set to True which makes it render a form tag for you. Meaning you are nesting form tags which does not work and is not possible with the HTML5 standard. You need to set this attribute to False to prevent this:
class OrderForm(ModelForm):
class Meta:
model = Order
fields = [
'__all__',
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper(self) # Explicitly set helper to prevent automatic creation
self.helper.form_tag = False # Don't render form tag
self.helper.disable_csrf = True # Don't render CSRF token
Next in the helper you make in the view you also have to set these attributes. Furthermore what you call as list_formset is not an instance of a formset but a class, hence you actually need to instantiate the formset class and use it:
#login_required
def orders(request):
template = f'{APP_NAME}/index.html'
list_helper = OrderListFormSetHelper()
list_helper.form_tag = False # Don't render form tag
list_helper.disable_csrf = True # Don't render CSRF token
OrderListFormSet = inlineformset_factory(Order,
OrderList,
OrderListForm,)
if request.method == 'POST':
form = OrderForm(request.POST, prefix="header")
list_formset = OrderListFormSet(request.POST, instance=form.instance) # Instantiate formset
if form.is_valid() and list_formset.is_valid():
order = form.save()
order_list = list_formset.save()
# Remove below two line, have already instantiated formset with `form.instance` and called save without `commit=False`
# order_list.order = order
# order_list.save()
return HttpResponseRedirect(reverse('order_created'))
else: # all other methods means we should create a blank form
form = OrderForm()
list_formset = OrderListFormSet(instance=form.instance) # Instantiate formset
return render(request, template, {'form': form,
'list_form': list_formset,
'list_helper': list_helper})

Django: Can I have two labels for a field?

I have a django form and I need to display unit of measurement.
For example, for a form field I have label and value. I also need to have another 'label' for unit measurement.
E.g. Weight [Textbox] Kg
How can I add Kg in a form in forms.py? I'm using crispy forms module to render my forms.
This is an example from forms.py .
class WeightForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(LifeEventsForm, self).__init__(*args, **kwargs)
self.helper=FormHelper(self)
self.helper.layout = Layout(
'weight',
FormActions(
Submit('submit', "Save changes"),
Submit('cancel',"Cancel")
),
)
self.helper.form_tag = False
self.helper.form_show_labels = True
class Meta:
model = myWeight
My models.py looks like:
class myWeight(models.Model):
id = models.IntegerField()
weight = models.IntegerField(null=True,blank=True)
def __str__(self):
return str(self.id)
Maybe not best idea, but pretty straightforward:
You can override from crispy_forms.layout import Field with custom template
class DoubleLabeledField(Field):
template = "your_custom_field.html"
copypaste to your_custom_field.html everything from .../site-packages/crispy_forms/templates/bootstrap3/field.html (replace bootstrap3 if another template package) and put some info near every entrance of {{ field.label|safe }}
Then in your form should be:
self.helper.layout = Layout(
DoubleLabeledField('weight'),
FormActions(
Submit('submit', "Save changes"),
Submit('cancel',"Cancel")
),
)

Make model form crispy

I'm trying to display all my model items but add some crispy elements. However it shows my html and buttons above the model form fields instead of being under the html and above the buttons.
Form
class BusinessForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(BusinessForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_class = 'form-horizontal'
self.helper.form_action = 'update'
self.helper.form_method = 'post'
self.helper.layout = Layout(
HTML("<p class='alert-info alert'>Please confirm your business contact information is updated and correct.</p>"),
Div(
FormActions(
Submit('save_changes', 'Save changes', css_class="btn-primary"),
),
css_class='row-fluid'
)
)
# self.helper.add_input(Submit('save_changes', 'Save changes', css_class="btn-primary"))
class Meta:
model = Business
exclude = ('inactive',)
View
def index(request, token):
try:
business = Business.objects.get(token__token=token)
except Token.DoesNotExist:
business = None
except Business.DoesNotExist:
business = None
return render(request, 'business/index.html', {'form': BusinessForm(instance=business)})
I saw use Crispy form with ModelForm however it displays my model fields below the formactions and I even tried an add_input which did the same thing. How would I get my model form fields to be displayed between the alert-info and above the submit button?

Categories

Resources