I am trying to create a simple charting web app using pygal and flask to chart some financial data I have stored in a mysql database. The data in the DB is hierarchical, and I want the app to start at the top of the hierarchy and allow the user to drill down by simply clicking on the relevant parts of the chart.
I am using flask to display the dynamically generated pygal charts.
Sample DB data:
guid | name | value | parentGuid | Description
------------------------------------------------
1234 | cat1 | 1 | null | Blah, blah
4567 | cat2 | 55 | null | asfdsa
8901 | cat3 | 22 | 1234 | asfdsa
5435 | cat4 | 3 | 8901 | afdsa
etc...
I have no problem drilling down the hierarchy using python + sql, but where I'm stumped is how I drill down using links in my pygal chart.
#app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
chart = graph_sub(request.form['guid'])
return render_template('Graph3.html', chart=chart)
else:
chart = graph_main()
return render_template('Graph3.html', chart=chart)
def graph_main():
""" render svg graph """
line_chart = pygal.HorizontalBar()
line_chart.title = 'Root Accounts'
RootAccts = GetRootAccounts() # Returns a list of lists containing accounts and account data.
for Acct in RootAccts:
line_chart.add({
'title': Acct[1], # Acct Name
'tooltip': Acct[4], # Acct description
'xlink': {'href': '/'} # Hyperlink that I want to pass POST data back to the form.
}, [{
'value': Acct[2]), # Acct Value
'label': Acct[4], # Acct Description
'xlink': {'href': '/'} # Hyperlink that I want to pass POST data back to the form.
}])
return line_chart.render()
def graph_sub(parentGuid):
### This works fine if I pass a parent GUID to it
### Now the question is how do I pass the guid to it from my pygal chart?
return line_chart.render()
So when I click on the links embedded in the pygal chart
'xlink': {'href': '/'}
How can I make it redirect back to the same page and pass the GUID of the selected account as POST data? Or is there another way to do this that doesn't involve POST?
The page reloading every time they click something so I'm hoping to keep this as simple as possible without having to involve ajax/java/etc... though if there are no other options I am open to it.
I don't have it coded yet, but there will also be some additional form controls added to the page that will allow the user to set date ranges to control how much data is displayed. I was planning to use POST to pass user input around, but before I get too far down that road, I need to figure out how I can manage this base functionality.
Thoughts?
Related
I saw that it is possible to access data from context.table from Behave when the table described in the BDD has a header. for example:
Scenario: Add new Expense
Given the user fill out the required fields
| item | name | amount |
| Wine | Julie | 30.00 |
To access this code it's simply:
for row in context.table:
context.page.fill_item(row['item'])
context.page.fill_name(row['name'])
context.page.fill_amount(row['amount'])
That works well and it's very clean, however, I have to refactor code when I have a huge amount of lines of input data. for example:
Given I am on registration page
When I fill "test#test.com" for email address
And I fill "test" for password
And I fill "Didier" for first name
And I fill "Dubois" for last name
And I fill "946132795" for phone number
And I fill "456456456" for mobile phon
And I fill "Company name" for company name
And I fill "Avenue Victor Hugo 1" for address
And I fill "97123" for postal code
And I fill "Lyon" for city
And I select "France" country
...
15 more lines for filling the form
How could I use the following table in behave:
|first name | didier |
|last name | Dubois |
|phone| 4564564564 |
So on ...
How would my step definition look like?
To use a vertical table rather than a horizontal table, you need to process each row as its own field. The table still needs a header row:
When I fill in the registration form with:
| Field | Value |
| first name | Didier |
| last name | Dubois |
| country | France |
| ... | ... |
In your step definition, loop over the table rows and call a method on your selenium page model:
for row in context.table
context.page.fill_field(row['Field'], row['Value'])
The Selenium page model method needs to do something based on the field name:
def fill_field(self, field, value)
if field == 'first name':
self.first_name.send_keys(value)
elif field == 'last name':
self.last_name.send_keys(value)
elif field == 'country':
# should be instance of SelectElement
self.country.select_by_text(value)
elif
...
else:
raise NameError(f'Field {field} is not valid')
I have a wizard in which there is a one2many field. I made a button in each line of the one2many which calls another wizard made by me. This wizard is for modifying some values of the selected line.
My purpose is to return the first wizard, with the new changes, when you click on the Apply button of my wizard.
Example:
The first wizard has a one2many field with three records:
Product A | 1 ud | Source location X | Dest location Y | Lot A1
Product B | 2 ud | Source location X | Dest location Y | Lot B1
Product C | 3 ud | Source location X | Dest location Y | Lot C1
Now, I click on the first line button I made (each line has one), and
my wizard is opened. Here I can modify the lot of the first line (the
one with the Product A). Imagine I set Lot A0 and click on Apply.
I should return to the parent wizard, and see the same data except for
the changes made. So the result will be:
Product A | 1 ud | Source location X | Dest location Y | Lot A0
Product B | 2 ud | Source location X | Dest location Y | Lot B1
Product C | 3 ud | Source location X | Dest location Y | Lot C1
Does anyone know how to achieve this? How could I preserve the first wizard data?
First you need to browse current record of Wizard and it's line. Afterward write value as you want.
Return that current id with wizard object.
Try with following trick:
#apply button method logic
def apply_data(self, cr, uid, ids, context=None):
if not context:
context = {}
ctx = context.copy()
for wizard in self.browse(cr, uid, ids[0], context=context):
for line in wizard.one2many_field:
line.write({
'field_name': field_value
})
dummy, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'module_name', 'wizard_form_view_name')
return {
'name':_("Name of your Wizard"),
'view_mode': 'form',
'view_id': view_id,
'view_type': 'form',
'res_id': ids and ids[0] or False,
'res_model': 'wizard.object.name',
'type': 'ir.actions.act_window',
'nodestroy': True,
'target': 'new',
'context': ctx
}
NOTE:
You can also update context value as well.
Apply button type must be object to execute method logic.
I have a Django view that uses one form multiple times. The form is saving relationship Subgroup id as a foreign key and Student id as a foreign key .
The problem I'm having is when I try to save information to database it only saves the last record.
For example (database model):
1 858 | Pump | Iron
2 78 | Madagaskar| Thomas
And if Im trying to split them into seperate groups, only Madagaskar his data is saved:
id | timestamp | student_Id_id | subgroup_Id_id |
+----+----------------------------+---------------+----------------+
| 62 | 2016-05-06 10:54:49.022000 | 2 | 91 |
The form looks like this:
class ApplicationFormaFull1(MultiModelForm):
form_classes = {
'sub1': FormSubgroup,
'sub2': FormSubgroup,
'stud_sub': FormStudent_in_Subgroup
}
and my view :
sub = form['sub1'].save(commit=False)
sub.student_group = StudentGroup.objects.get(id=element)
sub.number = 1
sub.type = 'Other'
sub.student_count = firstSubgroup
sub.save()
sub1 = form['sub2'].save(commit=False)
sub1.student_group = StudentGroup.objects.get(id=element)
sub1.number = 2
sub1.type = 'Others'
sub1.student_count = secondSubgroup
sub1.save()
if (counter%2==1):
stud_sub = form['stud_sub'].save(commit=True)
stud_sub.subgroup_Id = sub
stud_sub.student_Id = Student.objects.get(id=student)
stud_sub.save()
else:
stud_sub = form['stud_sub'].save(commit=True)
stud_sub.subgroup_Id = sub1
stud_sub.student_Id = Student.objects.get(id=student)
stud_sub.save()
So to sum up, I want that every form would save its information multiple times (dynamically)
Maybe the solution is that I should store information in the list and after all forms are added, save them one by one ?
stud_sub = form['stud_sub'].save(commit=False)
stud_sub.subgroup_Id = sub
stud_sub.student_Id = Student.objects.get(id=student)
list.add(stud_sub)
...
for i in list:
i.save()
Other solution use formset:
ArticleFormSet = formset_factory(ArticleForm, extra=2)
formset = ArticleFormSet(initial=[
{'title': 'Django is now open source',
'pub_date': datetime.date.today(),}
])
However i dont know how to change title, pub_date and to add everyting to formset dynimically.
I've got a question which I'll ask two ways: short & generic, so future generations of StackOverflow readers will benefit, and Long & Detailed, so I can get my work done without screwing anything up.
Short & Generic Version:
How do I make Django generate a table-like form where some info in the table is from the database, and the user fills in the rest? On form submission, each row in the table should become a record in the database (after it's validated, of course).
What's the cleanest way to do this? What's this mode of interaction cannonically called?
Example Form
|=============================================================|
| QueriedValue | CalculatedValue | User_data | More_User_data |
|_____________________________________________________________|
| Foo 1 | Bar 1 | | |
| Foo 2 | Bar 2 | | |
| Foo 3 | Bar 3 | | |
... ... ... ... |
| Foo n | Bar n | | |
|=============================================================|
++++++++++
| Submit |
++++++++++
Resulting Database Records
TimeStamp + fk_Foo = natural primary key for this table
________________
/ \
|===========================================================|
| TimeStamp | fk_Foo | User_data | More_User_data |
|___________________________________________________________|
| submit_time | Foo 1 | Datum 1 | AnotherDatum 1 |
| submit_time | Foo 2 | Datum 2 | AnotherDatum 2 |
| submit_time | Foo 3 | Datum 3 | AnotherDatum 3 |
|... ... ... ... |
| submit_time | Foo n | Datum n | AnotherDatum n |
|===========================================================|
Long Version
I'm writing a web app to track gas cylinder usage at my company. We have a bunch of gas plumbing in our building, and we need to know which gas cylinder was hooked up to which gas line at what time.
I'd like two forms for the technicians to fill out:
Daily Inventory: Every morning, the stockroom guy needs to look at each gas line and record the line's pressure, and the reference number of the bottle. This generates bunch of 4-tuple records (time, line, bottle, psi); one for each line, every morning.
As-Needed Bottle Change: After doing the daily inventory, if a bottle is almost out it needs to be changed, and that change needs to be logged. This should add another entry to the table of bottles for the new bottle, and another 4-tuple with the new (time, line, bottle, psi) info for the new connection. This happens to a random line a few times a week, but not every day.
So to keep track of this I'm writing a Django application. I've got the following models:
# models.py
class GasFarm(models.Model):
"""
Represents a gas farm -- a collection of lines that are grouped together and managed as a unit.
"""
name = models.CharField(max_length=30, unique=True)
def __unicode__(self):
return self.name
class Bottle(models.Model):
"""
Represents a gas bottle -- the physical cylinder -- that contains a mixture of gases.
"""
# Options
get_latest_by = 'date_added'
# Fields
BACKGROUND_TYPES = (
('h2/n2', "H2/N2"),
('h2/air', "H2/Air"),
('h2', "H2"),
('n2', "N2"),
('other', "Other"),
)
ppm = models.FloatField()
mix = models.CharField(max_length=50, choices=BACKGROUND_TYPES, default='n2')
ref = models.CharField(max_length=50, unique=True) # Every bottle has a unique ref or somebody fucked up.
cert_date = models.DateTimeField()
date_added = models.DateTimeField(default=timezone.now())
def pct(self):
return float(self.ppm)/10**4
def __unicode__(self):
return "{} ({}% {})".format(self.ref, self.pct(), self.mix,)
class Line(models.Model):
"""
Represents a gas line -- the physical plumbing -- that delivers gas from the bottles to the test stations.
It is assumed that a gas line can have zero or one gas bottles attached to it at any given time. The Line model
maps bottle objects and time-sensitive Reading objects to test stations.
"""
# Fields
gasfarm = models.ForeignKey(GasFarm)
number = models.CharField(max_length=10, unique=True)
bottles = models.ManyToManyField(Bottle, through='Reading')
# Calculated fields. "current" is definitely not optional -- that's a super common query. The others? I'm not so
# sure...
def current(self):
"""
Returns the most recently recorded Reading object associated with the line
"""
return self.reading_set.latest(field_name='time')
current.short_description = "latest reading"
def last_checked(self):
"""
Returns the date & time at which the most recent Reading object associated with this line was logged
"""
return self.current().time
last_checked.short_description = "last updated"
def has_recent_reading(self):
"""
Boolean flag for whether the reading is probably valid, or if someone needs to go out and take a new one.
"""
latest_reading = self.current().time
return timezone.now() - latest_reading < datetime.timedelta(days=3)
has_recent_reading.boolean = True
has_recent_reading.short_description = "Is data current?"
def __unicode__(self):
return self.number
class Reading(models.Model):
"""
A Reading links a Bottle to a Line at a given time, and provides a snapshot of the pressure at that time.
"""
# Options
get_latest_by = 'time'
# Fields
line = models.ForeignKey(Line)
bottle = models.ForeignKey(Bottle)
time = models.DateTimeField()
psi = models.IntegerField(validators=[MaxValueValidator(2500)])
def ref(self):
"""
The reference number of the bottle listed in the reading
"""
return self.bottle.ref
def ppm(self):
"""
The PPM concentration of the bottle listed in the reading
"""
return self.bottle.ppm
def pct(self):
"""
The % concentration of the bottle listed in the reading
"""
return self.bottle.pct()
def mix(self):
"""
The gas mix (e.g. H2/N2) of the associated bottle
"""
return self.bottle.mix
def __unicode__(self):
# Example:
# A0: 327.3 PPM H2/N2 2300 psi
return "{}, {}: {} PPM {} {} psi".format(self.line, self.time, self.ppm(), self.mix(), self.psi)
I've populated the database with our back-log of data using some scripts, and I've written a few views to pull data out of the databse; I'm happy with them so far, and the results look very promising -- at least for displaying stored data.
But I'm not sure how to cleanly populate the database using HTML forms. I'd like the forms to be basically two separate "worksheets" -- like the kind the DMV gives you, with nice clear instructions #justkidding.
Form 1: Daily Inventory
The form would list all the lines in a given farm, display what bottle should be on each line (based on previous readings/updates), and then prompt the user to enter a value. This would require that the technician update the pressure of every bottle on every line each time they submit the form -- we want a global snapshot of the whole gas system. In a perfect world, the form would pre-fill the current time and each line's most recent pressure reading into the Reading Time and Pressure fields to ease data entry.
# Cells with brackets [] are system-supplied, non-editable data displayed in the table.
# Cells without brackets are pre-filled with sensible defaults, but are user editable.
| [Line] | [Current Bottle] | Reading Time | Pressure (psi) |
===============================================================
| [A0] | [15-1478334] | 2014-7-14 9:34 | 2400 |
| [A1] | [15-1458661] | 2014-7-14 9:34 | 500 |
| [A2] | [15-4851148] | 2014-7-14 9:34 | 1850 |
| [A3] | [15-1365195] | 2014-7-14 9:34 | 700 |
...
...
| [C18] | [15-9555813] | 2014-7-14 9:34 | 2350 |
|=====================================================================|
After reading through the Django docs on Forms, ModelForms, and Formsets, I've written some code that does almost everything I want -- but the Line and Bottle information are editable form fields, and I need them to be static guideposts for filling in the rest of the form. They do need to be present in the generated database records, though.
I am dimly aware of the readonly and disabled attributes, and of what appear to be kludgy solutions to clean data from the POST variable in the response when you want to have read-only stuff in forms, but I'm still not clear on how those work or why they're necessary. I'm wondering if there's a cleaner way to get what I"m after? Perhaps forms with programmatically generated headings, or annotations? That's all I really want: an auto-generated guide to filling out the form.
# Forms.py
class PressureReadingUpdate(forms.ModelForm):
class Meta:
model = models.Reading
PsiReadingFormset = formset_factory(PressureReadingUpdate, extra=0)
# views.py
def update_pressure(request):
if request.method == 'POST':
formset = forms.PsiReadingFormset(request.POST)
if formset.is_valid():
cd = formset.cleaned_data
# do something? I'm not here yet...
else:
lines = models.Line.objects.all()
now = datetime.datetime.now()
initial = [{'line': l,
'psi': l.current().psi,
"bottle": l.current().bottle,
'time': now} for l in lines]
formset = forms.PsiReadingFormset(initial=initial,)
return render(request, 'gas_tracking/gasfarm_form_psi_reading.html', {'formset': formset})
Form 2: Change a Gas Bottle
I'd like a list of all the gas lines, with the current bottle & pressure (easy-- this is done elsewhere), and then a button that makes a pop-up window where you can submit a new bottle, much like you find in the admin interface. How do I make pop-up windows? How do I make buttons? I'm not even sure where to start with this one yet
I'm still very new to Django, and I've searched high and low, but haven't found anything that answers my question -- maybe I'm just not using the right keywords?
Thanks for your help.
-Matt
Dynamically generating Forms using FormSets
So I figured this out (after much googling and swearing and gnashing of teeth). Malcolm Tredinnick made a blog post about exactly what I wanted to do, which a kind soul preserved on Django Snippets
Using Malcom's code as a model, I solved my problem and it works like a charm
class PressureReadingUpdate(forms.Form):
"""
Form that asks for the pressure of a line given some attributes of that line.
"""
psi = forms.IntegerField(widget=forms.NumberInput)
def __init__(self, *args, **kwargs):
self.line = kwargs.pop('line')
kwargs['auto_id'] = "%s".format(self.line.number)
super(PressureReadingUpdate, self).__init__(*args, **kwargs)
class BasePsiReadingFormset(BaseFormSet):
"""
Formset that constructs a group of PressureReadingUpdate forms by taking a queryset
of Line objects and passing each one in turn to a PressureReadingUpdate form as it
gets constructed
"""
def __init__(self, *args, **kwargs):
self.lines = kwargs.pop('lines')
super(BasePsiReadingFormset, self).__init__(*args, **kwargs)
self.extra = len(self.lines)
self.max_num = len(self.lines)
def _construct_form(self, i, **kwargs):
kwargs['line'] = self.lines[i]
form = super(BasePsiReadingFormset, self)._construct_form(i, **kwargs)
return form
PsiReadingFormset = formset_factory(form=PressureReadingUpdate, formset=BasePsiReadingFormset)
This gives you a Formset with an extra kwarg you can pass down the chain of constructors. You can use it in a view (along with a more typical initial= kwarg) with:
formset = PsiReadingFormset(lines=lines,
initial=[{'psi': l.current().psi} for l in lines],
So here's the best explanation I can think of:
Any kwargs passed to a FormSet like (which gets made by the formset_factory function using a non-default 'BaseFormSet' as a blueprint) get passed along -- mostly unaltered -- to the __init__ method of whatever BaseFormSet the FormSet is based on.
This means you can define custom behavior in BaseFormSet.__init__, and you can relay runtime data to the BaseFormSet.__init__ method by passing it as a keyword argument to FormSet (that's the lines= kwarg in my example above). I use it to set an attribute on on formset (an instance of a FormSet based on 'BasePsiReadingFormset').
Confused yet? I was too at first.
But the real magic is understanding how _construct_forms works: A FormSet calls this function each time it wants to make a new Form in the set. It will relay any unrecognized kwargs to to the constructor of the Form it is supposed to manage a set of.
So you just need to overload your custom BaseFormSet's ._construct_forms to wrap the original _construct_forms of the superclass and inject a new kwarg. This kwarg will then be passed through to the constructor of your custom Form class, and shape the new Form instance according to the Form's initialization function.
And that, ladies and gentlemen, is how you can have a FormSet that has a bunch of similarly-defined but dynamically-generated-and-slightly different forms in it.
After understanding this, I can see the elegance of it. However, it uses some intermediate-to-advanced python, and is neither immediately obvious nor well documented. If you are struggling with this like I did, feel free to message me.
# ! /usr/bin/env python
# -*- coding: utf-8 -*-
# image_slide.py
""" Python 2.7.3
Cherrypy 3.2.2
"""
When an HTML form is submitted to a Python function, the values are sent within one dictionary. For some subelements in an HTML form, I want to POST an additional nested dictionary, within that form dictionary. The question is, if there is an HTML tag or such a thing, which Python would interpret as opening for a new dictionary. So far I tried naming the <td> or an invisible <fieldset>, but of course it isn't as simple. And nested forms aren't possible either.
What I have is a form, which contains a few text inputs and a table with a selection of associated items.
_____ the entire form __________________________________________________________
| |
| beverage: coffee |
| time of day: morning |
| temperature: hot |
| color: brown |
| taste: full-bodied |
| |
| Ingredients: |
| ________________________________________________________________________ |
| | | | | | |
| | <IMAGE_1> | <IMAGE_2> | <IMAGE_3> | <IMAG| |
| | | | | | |
| | filename: coffee | filename: sugar | filename: milk | filename:| |
| | x select item | x select item | x select item | x select | |
| |___________________|___________________|___________________|__________| |
| |_____________________________<scrollbar>______________________________| |
| |
| < OK > |
|______________________________________________________________________________|
Following are three simple pseudo-code snippets to explain the gist.
Content of one form entry:
beverages = {
'coffee': {
'beverage': 'coffee',
'time of day': 'morning',
'temperature': 'hot',
'color': 'brown',
'taste': 'full-bodied',
}
}
ingredients = {
'coffee': [
'coffee',
'sugar',
'milk',
'cinnamon',
],
}
Top part of the form :
yield(u'''
<form action="..." method="POST">
<table>
''')
for key, value in beverages['coffee'].items():
yield(u'''
<tr>
<td> {key}: </td>
<td> <input type="text" name="{key}" value="{value}">< /td>
</tr>
'''.format(locals(),)
Bottom part of the form :
""" This hole bottom part already shall be in a dictionary named 'ingredients'.
For each loop pass, I'd like a sub-dictionary, named by its loop pass.
I used the fictional tag <SPECIAL_SUB_DICT>, to open a new dictionary.
"""
yield(u'''
</table>
<SPECIAL_SUB_DICT name="ingredients">
<table style="overflow-x: scroll">
<tr>
''')
for index, name in enumerate(ingredients['coffee']):
yield(u'''
<td>
<SPECIAL_SUB_DICT name="{index}">
<img src="...">
<input type="text" name="filename" value="{name}">
<input type="check" name="chosen_one" value="{index}"> select item
</SPECIAL_SUB_DICT>
</td>
'''.format(locals(),)
yield(u'''
</tr>
</table>
</SPECIAL_SUB_DICT>
<input type="submit" name="submit" value="OK">
</form>
''')
Conclusion
The problem is, without some sort of <SPECIAL_SUB_DICT> tag, I can't easily rename single ingredients. For example, if I'd like to change the filename from 'milk' to 'hole cream milk'. The way I do it now is, to add the current loop pass to the input name, like this:
'<input type="text" name="filename_{index}" value="{name}"> '.format(locals(),)
Then, within the receiving function, I can check which key starts with 'filename_' and update all of them:
for key, values in kwargs:
if key startswith('filename_'):
ingredients['coffee'][key[9:]] = value
It would be much nicer, if I just could iterate kwargs['ingredients']:
for key, values in kwargs['ingredients'].items():
ingredients['coffee'][key] = value[filename]
I'm asking, because a <SPECIAL_SUB_DICT> tag would be much closer to my current solution than parsing the table with Python's BeautifulSoup. And of course I want to know it. After all, with BeautifulSoup I'd probably be done by now.
EDIT 1:
This runs with the web application framework CherryPy.
Maybe there's a way it could handle such a request.
Though I dont't think, that it would offer something off-standard.
EDIT 2:
Given, that the form-dictionary starts in the URL with a question mark, I don't think a sub-dictionary is possible, because I don't know of any dictionary closing character. The closest thing I could come up with is using a hidden input named 'index', and zip() it with the text input named 'filename'. This answer, unfortunately for PHP, got me in the right direction.
'<input type="hidden" name="index" value="{index}"> '.format(locals(),)
'<input type="text" name="filename" value="{name}"> '.format(locals(),)
for key, values in zip(kwargs['index'], kwargs['filename']):
ingredients['coffee'][key] = value
However, this wouldn't work for the delete button, which I planed to add to each ingredient. Since only the submit input can carry the index, and its value is used to display the button's unicode symbol, I again have to append the index to the name.
Then, the only thing I can think of, is a form for each ingredient and the top part of the formular, instead of one large form for everything. Though I don't know if that would be good for the performance, if the list of ingredients becomes too long.
As a visual trick comes a background image to mind, to replace the value, which then transparently, can be used according to its name. This answer and its corresponding question was helpful to get it working.
But nothing of this is an answer to my question.
I'm still missing the one solution for all inputs,
like multiple forms, just more elegant.
Okay, I wanted to know how this is done as well, so here you go. This only works for single-level dicts. If you want to make this recursive, I'll leave it to you.
The problem with this: <input name="foo[bar]"> is that foo[bar] is the variable name, and the brackets are converted to URL-safe entities:
[DEBUG] BODY_TEXT: foo%5Bbar%5D=input_value
CherryPy allows you to define a custom body processor by defining a callable in cherrypy.request.body.processors, which is a dictionary whose keys match the content type. This is how the cherrypy.tools.json_in() tool works, by replacing the body processor with a function that parses JSON when the request's content type is application/json.
The final part you need is how to inject your parsed dictionary (i.e. {'foo': 'bar'} into the handler method's arguments to be used just like regular POST input vars. I took it directly from the default form processor: https://github.com/cherrypy/cherrypy/blob/main/cherrypy/_cpreqbody.py#L177
So here's the tool. You need to enable it in your config, and load it in your server script, but it will take a dict-shaped set of form inputs pass the dict to your handler. It does this with a regular expression.
import re
import cherrypy
import logging
import urllib
class ImprovedFormInput(cherrypy.Tool):
def __init__(self):
logging.debug('ImprovedFormInput.__init__()')
cherrypy.Tool.__init__(self, 'before_request_body', self.handle_post, priority=50)
def handle_post(self):
logging.debug('ImprovedFormInput.handle_post()')
request = cherrypy.serving.request
def impfin_processor(entity):
raw_body = entity.fp.read()
body_text = raw_body.decode()
logging.debug('BODY_TEXT: %s', body_text)
parsed = urllib.parse.parse_qs(body_text)
logging.debug('PARSED: %s', parsed.keys())
form_inputs = {}
r = re.compile(r'^(\w+)\[(.+)\]$') # This pattern could be better
for key in parsed.keys():
m = r.match(key)
if m is None:
continue
groups = m.groups()
pkey = groups[0]
logging.debug('PKEY: %s', pkey)
ckey = groups[1]
logging.debug('CKEY: %s', ckey)
if pkey not in form_inputs:
form_inputs[pkey] = {}
form_inputs[pkey][ckey] = parsed[key]
logging.debug('FINPUTS: %s', form_inputs)
# https://github.com/cherrypy/cherrypy/blob/main/cherrypy/_cpreqbody.py#L177
for key, value in form_inputs.items():
if key in entity.params:
if not isinstance(entity.params[key], list):
entity.params[key] = [entity.params[key]]
entity.params[key].append(value)
else:
entity.params[key] = value
request.body.processors['application/x-www-form-urlencoded'] = impfin_processor