Generate a dynamic form using flask-wtf and sqlalchemy - python

I have a webapp that allows users to create their own fields to be rendered in a form later on.
I have a model Formfield like so:
class Formfield(db.Model):
id = db.Column(db.Integer, primary_key = True)
form_id = db.Column(db.Integer, db.ForeignKey('formbooking.id'))
label = db.Column(db.String(80))
placeholder_text = db.Column(db.String(80))
help_text = db.Column(db.String(500))
box_checked = db.Column(db.Boolean, nullable = True, default = False)
options = db.Column(db.String) # JSON goes here?
answer = db.Column(db.String)
required = db.Column(db.Boolean, nullable = False, default = False)
order = db.Column(db.Integer)
fieldtype = db.Column(db.Integer)
that I use to represent a field, whatever the kind (checkbox, input, more in the future).
As you can see, every field has a FK to a form_id.
I’m trying to generate a dynamic form for a given form_id.
The catch is that I need to determine the type of field to render for every Formfield.
So I also need to process the fieldtype at some point.
I guess a solution would be to somehow pass the form_id to a function in my Form class.
I have no clue how to do it or where to look for a solution.
Any help would be very appreciated!

I think I managed to create dynamic forms with the idea from here https://groups.google.com/forum/#!topic/wtforms/cJl3aqzZieA
you have to create a dynamic form at view function, fetch the form field you want to get, and iterate every field to construct this form object. I used for fieldtypes simple text instead of integer values. Since it seems easy to read at code level.
class FormView(MethodView):
def get(self):
class DynamicForm(wtforms.Form): pass
dform = main.models.Form.objects.get(name="name2")
name = dform.name
for f in dform.fields:
print f.label
setattr(DynamicForm , f.label, self.getField(f))
d = DynamicForm() # Dont forget to instantiate your new form before rendering
for field in d:
print field # you can see html output of fields
return render_template("customform.html", form=d)
def getField(self, field):
if field.fieldtype == "text":
return TextField(field.label)
if field.fieldtype == "password":
return PasswordField(field.label)
# can extend if clauses at every new fieldtype
for a simple form render jinja template 'forms.html'
{% macro render(form) -%}
<fieldset>
{% for field in form %}
{% if field.type in ['CSRFTokenField', 'HiddenField'] %}
{{ field() }}
{% else %}
<div class="form-group {% if field.errors %}error{% endif %}">
{{ field.label }}
<div class="input">
{% if field.name == "body" %}
{{ field(rows=10, cols=40) }}
{% else %}
{{ field() }}
{% endif %}
{% if field.errors or field.help_text %}
<span class="help-inline">
{% if field.errors %}
{{ field.errors|join(' ') }}
{% else %}
{{ field.help_text }}
{% endif %}
</span>
{% endif %}
</div>
</div>
{% endif %}
{% endfor %}
</fieldset>
{% endmacro %}
and customform.html is like this
{% extends "base.html" %}
{% import "forms.html" as forms %}
{% block content %}
{{ forms.render(form) }}
{% endblock %}

Related

How to subtract from a Django model?

I am trying to add and subtract 1 from the value of a Django model's IntegerField and display it on the webpage, depending on when a button is clicked.
models.py
class User(AbstractUser):
following = models.ManyToManyField("self", related_name="followers", symmetrical=False)
following_num = models.IntegerField(default=0)
In views.py:
To add: following_num = page_visitor.following_num +1
To subtract: following_num = page_visitor.following_num -1
This is displaying in the html where the number should be displaying:
<django.db.models.query_utils.DeferredAttribute object at 0x0000022868D735E0>
entire view:
def username(request, user):
#get user
user = get_object_or_404(User.objects, username=user)
posts = Post.objects.filter(user=user).order_by('-date_and_time')
page_visitor = get_object_or_404(User.objects, username=request.user)
if user == request.user:
followButton = False
else:
followButton = True
if request.method == "POST":
if "follow" in request.POST:
request.user.following.add(user)
following_num = page_visitor.following_num +1
#following_num = F('User.following_num') + 1
elif "unfollow" in request.POST:
request.user.following.remove(user)
following_num = page_visitor.following_num -1
#following_num = F('User.following_num') - 1
followers_num = page_visitor.following_num
following_num = User.following_num
return render(request, "network/username.html",{
"user": user,
"posts": posts,
"followButton": followButton,
"followers": followers_num,
"following": following_num
})
html
{% block body %}
<h1>{{ user }}</h1>
<h4>Followers:{{ followers }}</h4>
<h4>Following:{{ following }}</h4>
<!--follow/unfollow button-->
{% if followButton == True %}
<form action = "{% url 'username' user %}" method = "POST">
{% csrf_token %}
{% if user not in request.user.following.all %}
<input type="submit" value="Follow" name="follow">
{% else %}
<input type="submit" value="Unfollow" name="unfollow">
{% endif %}
</form>
{% endif %}
<!--displays all the posts-->
{% for post in posts %}
<div class = "individual_posts">
<h5 class = "post_user">{{ post.user }}</h5>
<h6 id = "post_itself">{{ post.post }}</h6>
<h6 class = "post_elements">{{ post.date_and_time }}</h6>
<h6 class = "post_elements">{{ post.likes }} Likes</h6>
</div>
{% endfor %}
{% endblock %}
you reference the class (model) and not the object (instance)
following_num = User.following_num
as you alredy passed your user object to the template you can also access the attributes directly
{{user.following_num }} {{request.user.following_num }}
but you better rename the passed user variable to avoid confusion / errors
Get user information in django templates
Without the html that is supposed to display the number I cant tell why it is displaying that, but if you intend to change the following count then you need to call page_visitor.save() after changing a property of page_visitor in order for it to save the new property's value in the database

Display unique values on flask jinja template

I have a query on my views.py:
query = db.session.query(Basket, Fruits).filter(Fruits.basket_id == Basket.id)
In the html template, I wish to display for each basket id (basket id here is unique), what are the fruits inside each basket, thus I used a nested for loop in my template.
I have tried on the html template:
{% for basket in query | unique %}
<p>{{ basket.Basket.id }}</p>
{% for fruits in query %}
<p>{% if basket.Basket.id == fruits.Basket.id %}
<p>{{ query.Fruits.fruit }}</p>
{% endif %}
{% endfor %}
But the unique basket id is not displayed.
I have thought of creating 2 queries so that I can put .distinct and display the unique basket id, then use the other query to display the fruits, but that didn't make sense to me because I still need all the information in the whole query. Beginner in flask btw.
You might be able to achieve that with a one to many relationship.
class Basket(db.Model):
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(100))
fruits = db.relationship('Fruit', backref='basket')
class Fruit(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
basket_id = db.Column(db.Integer, db.ForeignKey('basket.id'))
#app.route('/')
def baskets():
baskets = db.session.query(Basket).all()
return render_template('baskets.html', baskets=baskets )
Jinja2 Template:
{% for b in baskets %}
<ul>
<p>
{% b.name %}
</p>
{% for f in b.fruits %}
<li>{{ f.name }}</li>
{% endfor %}
</ul>
{% endfor %}

Django Form Wizard: why done() method is not called after submitting?

I'm using a SessionWizardView and I can't understand why the done()method is never called. Instead, after posting my form, in the last step, I can see a POST HTTP 200 on my server, but this does nothing.
The get_form() method works as expected.
I suspect a distraction error since I have the exact same logic for another view, and this works well.
Here is the whole code bellow.
The view
class DiscountsCreateView(PermissionRequiredCanHandleProducts,
ModelInContextMixin,
RestaurantMixin, SubSectionDiscounts,
SessionWizardView):
""" Wizard view to create a discount in 2 steps """
model = Discount # used for model context
form_list = [DiscountForm0, DiscountForm1]
template_name = "discounts/discount_add.html"
def get_form(self, step=None, data=None, files=None):
form = super().get_form(step, data, files)
if step is None:
step = self.steps.current
# step0 - name, kind, tax_rate
# => nothing special to do, always the same form
# step1 - specific fields related to the chosen kind
if step == '1':
step0_data = self.storage.get_step_data('0')
kind = step0_data['0-kind']
# combo => combo, combo_unit_price
if kind == Discount.COMBO:
form.fields['combo'].queryset = Combo.objects.restaurant(self.restaurant)
# NOTE : this is not a scalable way to show/hide fields (exponential)
form.fields['rebate_amount'].widget = forms.HiddenInput()
elif kind == Discount.REBATE:
form.fields['combo'].widget = forms.HiddenInput()
form.fields['combo_unit_price'].widget = forms.HiddenInput()
return form
def done(self, form_list, **kwargs):
data = [form.cleaned_data for form in form_list]
try:
Discount.objects.create(
name=data[0]['name'],
kind=data[0]['kind'],
tax_rate=data[0]['tax_rate'],
rebate_amount=data[1]['rebate_amount'],
combo=data[1]['combo'],
combo_unit_price=data[1]['combo_unit_price']
)
except Exception as e:
messages.add_message(self.request, messages.ERROR, MSG_DISCOUNT_ADD_KO.format(e))
else:
messages.add_message(self.request, messages.SUCCESS, MSG_DISCOUNT_ADD_OK)
return redirect(reverse('bo:discount-list'))
The forms
class DiscountForm0(forms.Form):
name = forms.CharField(
label=verbose_display(Discount, 'name'))
kind = forms.ChoiceField(
label=verbose_display(Discount, 'kind'),
choices=Discount.KIND_CHOICES)
tax_rate = forms.ModelChoiceField(
label=verbose_display(Discount, 'tax_rate'),
queryset=TaxRate.objects.all())
class DiscountForm1(forms.Form):
"""
Contains all the specific fields for all discount kinds.
The goal is to only show the fields related to the right discount kind
"""
# For REBATE kind only
rebate_amount = forms.DecimalField(
label=verbose_display(Discount, 'rebate_amount'),
validators=[MaxValueValidator(0)])
# For COMBO kind only
combo = forms.ModelChoiceField(
label=verbose_display(Discount, 'combo'),
queryset=Combo.objects.none())
combo_unit_price = forms.DecimalField(
label=verbose_display(Discount, 'combo_unit_price'),
validators=[MinValueValidator(0)])
The templates
add_discount.html
{% extends "base_dashboard.html" %}
{% load verbose_name %}
{% block dashboard_title %}
Créer une {% model_name model %} : étape {{ wizard.steps.step1 }} / {{ wizard.steps.count }}
{% endblock dashboard_title %}
{% block dashboard_content %}
<form action='' method='post' novalidate>
{% csrf_token %}
{% include 'includes/_wizard_form_horizontal.html' with wizard=wizard %}
</form>
{% endblock dashboard_content %}
_wizard_form_horizontal.html
{{ wizard.management_form }}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{% include 'includes/_form_horizontal.html' with form=form %}
{% endfor %}
{% else %}
{% include 'includes/_form_horizontal.html' with form=wizard.form %}
{% endif %}
{% if wizard.steps.prev %}
<button class="btn btn-primary" name="wizard_goto_step" type="submit"
value="{{ wizard.steps.prev }}">
« étape précédente
</button>
{% endif %}
<input type="submit" class="btn btn-primary" value="étape suivante »"/>
The done() method is always called if the form submitted in the last step is_valid(). So if it's not, it must mean your form isn't valid.
In your case, you're hiding fields that are required by your DiscountForm1. So you're also hiding the error for these fields. You should make them optional and check in the form's clean() method if the appropriate fields are filled.

How to sort based on a choice field in django in template

Consider the following model:
class Category(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
POSITIONS = (
('L', 'Left'),
('R', 'Right')
)
position = models.CharField(max_length=1, choices=POSITIONS, default='R')
class Meta:
verbose_name_plural = 'categories'
def __str__(self):
return self.name
I want to sort the objects in a manner such that I can separate the left ones from the right ones and show them in separate parts of the html document. Something like:
{% for category in categories|sort_by: "position" = "left" %}
<ul class="collection with-header">
<li class="collection-header">
<h3>{{category.name}}</h3>
<p><small>{{category.description}}</small></p>
</li>
{% for url in category.extra_link_set.all %}
<li class="collection-item">{{url.url_text}}</li>
{% endfor %}
</ul>
{% endfor %}
I know this wont work. But I am just trying to give an idea as to what I want. Also would the process be similar for any other field type in django models?
You could try something like this:
{% for category in categories %}
<div class="left_stuff">
{% if category.position=='L' %}
<h3>...</h3>
.....
{% endif %}
</div>
{% endfor %}
{% for category in categories %}
<div class="right_stuff">
{% if category.position=='R' %}
<h3>...</h3>
.....
{% endif %}
</div>
{% endfor %}

Returning more than one field from Django model

How would one go about returning more than one field from a Django model declaration?
For instance: I have defined:
class C_I(models.Model):
c_id = models.CharField('c_id', max_length=12)
name = models.CharField('name', max_length=100)
def __str__(self):
return '%s' % (self.name)
which I can use, when called in a views.py function to return
{{ c_index }}
{% for c in c_index %}
<div class='c-index-item' c-id=''>
<p>{{ c.name }}</p>
</div>
{% endfor %}
from a template. This is being called in my views.py like so:
c_index = C_I.objects.all()
t = get_template('index.html')
c = Context({'c_index': c_index})
html = t.render(c)
How would I also include the c_id defined in the model above, so that I can include {{c.name}} and {{c.c_id}} in my template? When I remove the str method, I appear to get all of the results, however, it just returns blank entries in the template and will not let me reference the individual components of the object.
What you have is fine. Your template should be:
{% for c in c_index %}
<div class='c-index-item' c-id='{{ c.c_id }}'>
<p>{{ c.name }}</p>
</div>
{% endfor %}

Categories

Resources