HTML ─ Python dictionary from form-subelement via CherryPy - python

# ! /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

Related

add prefix to id in template if flag is set in django

My database was something like this :
| id | customer_account | -few more fields - | is_renewed |
| 25 | asd111 | - some values - | 0 |
| 26 | asd222 | - some values - | 1 |
| 27 | asd333 | - some values - | 1 |
| 28 | asd444 | - some values - | 0 |
in my models, I have :
class Policy(models.Model):
customer_account = models.ForeignKey(CustomerAccount, on_delete=models.CASCADE)
--few more fields--
is_renewed = models.BooleanField(default = False)
def use_updated_id(self):
if self.is_renewed:
new_id = str("R") + str(self.id)
else:
new_id = self.id
return new_id
in my template, I have :
{% for policy in policy_list % }
<p> Policy Number : {{policy.id}} </p>
{% endfor %}
which gives me output as
Policy Number : 25
Policy Number : 26
Policy Number : 27
Policy Number : 28
I understand that I can define a method in model and use that instead of id as below to meet my requirement
{% for policy in policy_list % }
<p> Policy Number : {{policy.use_updated_id}} </p>
{% endfor %}
Policy Number : 25
Policy Number : R26
Policy Number : R27
Policy Number : 28
My only challenge is that if use model method as above, i will have to replace updated multiple templates.
I'm looking for a better solution where in i only have to make changes in models file instead of updating multiple templates to achieve the desired result.
So you have {{ policy.id }} in multiple templates and want to change its behavior by making changes to models.py?
AFAIK you cannot achieve that, since you haven't correctly encapsulated the display beforehand. That's a pain, but you'll have to change it everywhere, since you're accessing a particular attribute on your models. Adding your use_updated_id is a great idea, since it encapsulates the display logic in one function and, in the future, if you need to change the display all you have to do is to change your new function.
So go on, make those hundreds of file edits but be sure that now you've made a great progress and facilitated your project maintainability.

How to fix twint error "CRITICAL:root:twint.get:User:replace() argument 2 must be str, not None"

I'm trying to use the twint module to get some information from twitter, in particular the bio. The code example works just fine:
import twint
c = twint.Config()
c.Username = "twitter"
twint.run.Lookup(c)
yields
783214 | Twitter | #Twitter | Private: 0 | Verified: 1 | Bio: What’s happening?! | Location: Everywhere | Url: https://about.twitter.com/ | Joined: 20 Feb 2007 6:35 AM | Tweets: 10816 | Following: 140 | Followers: 56328970 | Likes: 5960 | Media: 1932 | Avatar: https://pbs.twimg.com/profile_images/1111729635610382336/_65QFl7B_400x400.png
Thing is, I only need the bio data. According to the site, you can use
c.Format = 'bio: {bio}'
Unfortunately, this yields
CRITICAL:root:twint.get:User:replace() argument 2 must be str, not None
I think this may be due to the following code line (from here):
output += output.replace("{bio}", u.bio)
Where the u.bio value is assigned here:
u.bio = card(ur, "bio")
The card function does the following when our type is "bio":
if _type == "bio":
try:
ret = ur.find("p", "ProfileHeaderCard-bio u-dir").text.replace("\n", " ")
except:
ret = None
I think the problem may lie in the second part, where a value is assigned to u.bio, either not even being called or returning None for some reason. Unfortunately, I do not know how to fix that or call the function.
I've had a similar problem before with a different function, twint.run.Following(c), but was able to solve it by not setting c.User_full = true
Could anyone help me out?
The format should be of the form
c.Format = "{bio}"
If you wanted multiple fields
c.Format = "{bio} | {name}"
I find you get a rate limit of 250 items before a blocker drops down and you need to wait for a few minutes for it to lift.

Pygal - Click bar and post data?

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?

Parsing recursive templates with pyparsing

I'm currently trying to parse recursive templates with pyparsing. A template can look like this:
{{Attribute
| name=attr1
| description=First attribute.}}
The template has a name (Attribute) and defines some variables (name = attr1, description = First attribute.). However, there are also templates which can contain zero or more templates:
{{Enum
| name=MyEnum
| description=Just a test enum.
| example=Not given...
| attributes={{Attribute
| name=attr1
| description=First attribute.}}
{{Attribute
| name=attr2
| description=Second attribute.}}}}
To parse these templates I came up with the following:
template = Forward()
lb = '{{'
rb = '}}'
template_name = Word(alphas)
variable = Word(alphas)
value = CharsNotIn('|{}=') | Group(ZeroOrMore(template))
member = Group(Suppress('|') + variable + Suppress('=') + value)
members = Group(OneOrMore(member))
template << Suppress(lb) + Group(template_name + members) + Suppress(rb)
This works quite well, but it does not allow me to use "|{}=" within a value which is problematic if I want to use them. E.g.:
{{Enum
| name=MyEnum
| description=Just a test enum.
| example=<python>x = 1</python>
| attributes=}}
So, how can I change my code so that it allows these characters, too? Unforunately, I have no idea how I can archieve this.
I hope someone can give me some tips!
I found what I was looking for: https://github.com/earwig/mwparserfromhell

Django -- Generate form based on queryset

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.

Categories