Generate Wagtail Field's using a FOR loop - python-3.x

I am new to Wagtail and want to create several Field's in my models.py by iterating over a list of names like this ...
class HomePage(Page):
myFields = [ ('paragraph', blocks.RichTextBlock(classname="oneA")),('image', ImageChooserBlock()) ]
mySections = ['sectionOne', 'sectionTwo', 'sectionThree']
for mySection in mySections:
mySection = StreamField(myFields,null=True,blank=True)
content_panels = Page.content_panels + [
StreamFieldPanel('sectionOne'), StreamFieldPanel('sectionTwo'), StreamFieldPanel('sectionThree'),
]
This produces an error message ...
django.core.exceptions.FieldDoesNotExist: HomePage has no field named 'sectionOne'
Is there a way of doing this, or do I have to declare each one individually like so:
sectionOne = StreamField(myFields,null=True,blank=True)

This doesn't work because mySection = StreamField(...) is just repeatedly defining a field called mySection - there's no way for Python to know that you want to define a field with the name currently given in mySection.
I think the only way to achieve this is to set the fields up in a metaclass, which will almost certainly be more complex and less readable than just repeating the line. See How to dynamically set attributes to a class in models in Django?, Dynamically create class attributes

Related

I want to make a dropdown container in my script

I want to create a dropdown container to organize my export variable. Is it possible to create a custom dropdown container in the script?
Like this:
This is another approach to do this. It also requires the script to be tool.
What we need for this approach as a common prefix for the variables you want to group. The advantage is that we don't need _get and _set:
tool
extends Node
var custom_position:Vector2
var custom_rotation_degrees:float
var custom_scale:Vector2
func _get_property_list():
return [
{
name = "Custom",
type = TYPE_NIL,
hint_string = "custom_",
usage = PROPERTY_USAGE_GROUP
},
{
name = "custom_position",
type = TYPE_VECTOR2
},
{
name = "custom_rotation_degrees",
type = TYPE_REAL
},
{
name = "custom_scale",
type = TYPE_VECTOR2
}
]
As you can see we define a category with a name that will appear in the Inspector panel, and the hint_string is the prefix we will use. It is important to put the category before the properties in the array.
See: Adding script categories
Addendum: Using PROPERTY_USAGE_CATEGORY will produce a named header, similar to the one that says "Node2D" on the picture on the question. Use PROPERTY_USAGE_GROUP to make a collapsible group.
Yes, you can do this, but (in my opinion) it is a bit ugly and clutters up your script. You need to mark your script as a tool script and override the _get, _set, and _get_property_list functions.
An example based on your screenshot (not 100% sure this works exactly as-is; I'm also basing it on a recent project where I have since removed it and somewhat reorganized the project/code/node because the slightly nicer UI wasn't worth the additional clutter in the script):
tool
extends Node2D
# Note that these are NOT exported
var actual_position: Vector2
var actual_rotation: float
var actual_scale: Vector2
# Function to enumerate the properties to list in the editor
# - Not actually directly/automatically backed by variables
# - Note the naming pattern - it is {group heading}/{variable}
func _get_property_list():
var props = []
props.append({name="transform/position", type=TYPE_VECTOR2})
props.append({name="transform/rotation deg", type=TYPE_FLOAT}) # might not exist; look at docs to determine appropriate type hints for your properties
props.append({name="transform/scale", type=TYPE_VECTOR2})
return props
# Now the get/set functions to map the names shown in the editor to actual script variables
# Property names as input here will match what is displayed in the editor (what is enumerated in _get_property_list); just get/set the appropriate actual variable based on that
func _get(property: String):
if property == "transform/position":
return actual_position
if property == "transform/rotation deg":
return actual_rotation
if property == "transform/scale":
return actual_scale
func _set(property: String, value):
if property == "transform/position":
actual_position = value
return true
if property == "transform/rotation deg":
actual_rotation = value
return true
if property == "transform/scale":
actual_scale = value
return true
# Not a supported property
return false
Note that this answer is based on Godot 3.4. I'm not sure if a simpler approach is (or will be) available in Godot 4.

How to initialize object attributes from an array of strings in Python?

I am supposed to write a class which will allow the user of the script to define what attributes will it's objects have when initializing them by giving it an array of strings like this.
data = Database(tables=['distance', 'speed'])
Then it should be possible to call the class's methods like
data.distance.insert({1: 25, 2: 55})
data.speed.mean()
etc.
I have tried using setattr() this way
data = Database()
tables=['distance', 'speed']
for item in tables:
setattr(data, item, item)
which works, but isn't exactly what it should be.
Any ideas how to do it directly inside the class?

Is there a pattern for declaring a computed key for a model?

I'd like a particular ndb.Model key's string id to be automatically set to the concatenation of two of the models' properties. These two properties are write-once, although other properties might change. This approach helps ensure that the entities are unique for these two properties.
Here's how the class might look like:
class Foo(ndb.Model):
bar: ndb.StringProperty()
baz: ndb.StringProperty()
An entity could be constructed like this:
foo = Foo(id='bar-baz', bar='bar', baz='baz')
foo.put()
Is there a pattern to automatically set that id in the model class itself, similar to a ComputedProperty?
You can use the _pre_put_hook function to set the key name as part of a put. This would also be the place to verify that bar & baz do not change as part of an update.

Web2Py list:reference table, Load and set data

Maybe I'm missing something absurd, I'm not seeing, but this is my first app to study web2py.
I am unable to enter the data in Table Movies, which has fields related to other tables.
The list is loaded, but it is not registered in Movies registration.
Under the codes and the results.
db.py
Movie = db.define_table('movies',
Field('title','string', label = 'Title'),
Field('date_release','integer', label = 'Date Release'),
Field('duraction','integer', label = 'Duraction'),
Field('category','string','list:reference categories', label = 'Category'),
Field('actor','list:reference actors', label = 'Actor'),
Field('director','list:reference directors', label = 'Diretor'),
)
Category = db.define_table('categories',
Field('title','string', label = 'Title'),
)
validators.py
Movie.title.requires = [IS_NOT_EMPTY(), IS_NOT_IN_DB(db, 'movies.title')]
Movie.category.requires = IS_IN_DB(db, 'categories.title')
Movie.director.requires = IS_IN_DB(db, 'directors.name')
Movie.actor.requires = IS_IN_DB(db, 'actors.name')
Movie.duraction.requires = IS_INT_IN_RANGE(0, 1000)
Category.title.requires = IS_NOT_EMPTY()
movie.py
def add():
form = SQLFORM(Movie)
if form.process().accepted:
response.flash = "Successful! New movie added!"
redirect(URL('add'))
elif form.errors:
response.flash = 'Error'
else:
response.flash = 'Form, set data'
return dict(form = form)
List Load another tables - ok:
The items of list not record in DB:
The widgets displayed in the form are based on the IS_IN_DB field validators you have specified, and there are three problems with the way you have coded them.
First, list:reference fields, like standard reference type fields, store the record IDs of the records they reference -- they do not store values of other fields within the referenced records. So, the second argument to the IS_IN_DB validator should always be the ID field (e.g., categories.id).
Second, although the field will store record IDs, you want the form widget to show some other more descriptive representation of each record, so you should specify the "label" argument of the IS_IN_DB validator (e.g., label='%(title)s').
Third, list:reference fields allow for multiple selections, so you must set the "multiple" argument of the IS_IN_DB validator to True. This will result in a multi-select widget in the form.
So, the resulting validator should look like this:
Movie.category.requires = IS_IN_DB(db, 'categories.id', label='%(title)s', multiple=True)
The above will allow multiple db.categories IDs to be selected, though the form widget will display category titles rather than the actual IDs.
Now, all of the above can be made much easier if you instead define the referenced tables before the db.movies table and specify a format argument for each table:
Category = db.define_table('categories',
Field('title','string', label = 'Title'),
format='%(title)s')
Movie = db.define_table('movies',
...,
Field('category', 'list:reference categories', label = 'Category'),
...)
With the above code, there is no need to explicitly specify the IS_IN_DB validator at all, as the db.movies.category field will automatically get a default validator exactly like the one specified above (the format attribute of the db.categories table is used as the label argument).
You might want to read the documentation on list:reference fields and the IS_IN_DB validator.
As an aside, you might consider specifying your field validators within the table definitions (via the requires argument to Field()), as this is more concise, keeps all schema-related details in one place, and eliminates the need to read and execute an additional model file on every request.

How do I find all elements which have a css class in coded ui?

I need to find all Html controls which have a given css class.
var htmlControl = new HtmlControl(document);
htmlControl.SearchProperties[HtmlControl.PropertyNames.Class] = #class;
var uiTestControlCollection = htmlControl.FindMatchingControls();
Using the class name works when there is just one css class on the control. If I have more than one css classes applied on the element, can I search for the element by specifying just one css class and not all of them?
Thanks
You can perform a partial match, like so:
htmlControl.SearchProperties.Add(HtmlControl.PropertyNames.Class, #class, PropertyExpressionOperator.Contains);
var uiTestControlCollection = htmlControl.FindMatchingControls();
The main draw back of this is that it is just a simple string compare. To illustrate, imagine you have two controls A and B. A has class "Test" and B has classes "testdiv topnav". Now if you perform a search for "test", both controls A and B will be selected.
To match a class exactly, you can provide a close as match as possible using the above method and write a helper function to:
Loop through the collection
Get the class of each control
Split the class string on the spaces
Loop through this array and test each for an exact match
Keep the elements where a class matches exactly
Note: This is clearly non-optimal - I'm all ears if someone has a better solution.
Cheers,
Seb

Resources