13_Developer_Guide - Anisan/osysHome-Users GitHub Wiki
Relevant source files
The following files were used as context for generating this wiki page:
This Developer Guide provides information for engineers who want to extend or modify the osysHome-Users plugin. The guide covers the plugin architecture, extension points, and development patterns. For information about using the user management system as an administrator, see User Management Plugin and for details on the role system, see Role-Based Access Control.
The osysHome-Users plugin follows a modular design pattern built on Flask. Understanding this architecture is essential for making modifications or extensions.
classDiagram
class BasePlugin {
+__init__(app)
+initialization()
+admin(request)
+render(template, content)
}
class Users {
+__init__(app)
+initialization()
+admin(request)
-handle_add_user()
-handle_edit_user()
-handle_delete_user()
-handle_set_password()
-handle_upload_image()
}
class UserForm {
+username: StringField
+image: StringField
+role: SelectField
+home_page: StringField
+apikey: StringField
+timezone: StringField
+submit: SubmitField
}
class PasswordForm {
+password: StringField
+repeat_password: StringField
+submit: SubmitField
}
BasePlugin <|-- Users
Users --> UserForm: uses
Users --> PasswordForm: uses
The Users plugin inherits from BasePlugin and connects to form classes for data validation and collection.
Sources: init.py:19-95, forms/UserForm.py:6-13, forms/PasswordForm.py:6-9
The plugin uses an operation-based routing system where a single endpoint handles multiple operations through the op
query parameter.
flowchart TD
A["admin(request)"] --> B{"op parameter?"}
B -->|"op=add"| C["Create new user"]
B -->|"op=edit"| D["Edit existing user"]
B -->|"op=delete"| E["Delete user"]
B -->|"op=setPassword"| F["Change password"]
B -->|"op=upload_image"| G["Upload avatar"]
B -->|"no op"| H["List all users"]
C -->|"POST"| C1["Validate form"]
C1 -->|"valid"| C2["addObject()"]
C1 -->|"invalid"| C3["Re-render form"]
D -->|"POST"| D1["Validate form"]
D1 -->|"valid"| D2["setProperty()"]
D1 -->|"invalid"| D3["Re-render form"]
E --> E1["deleteObject()"]
F -->|"POST"| F1["Validate form"]
F1 -->|"valid"| F2["set_password()"]
F1 -->|"invalid"| F3["Re-render form"]
G -->|"POST"| G1["Save file"]
G1 --> G2["Update image URL"]
Understanding this flow is essential when adding new operations or modifying existing ones.
Sources: init.py:31-94
To add new properties to the user model:
- Extend the
UserForm
class with new fields. - Update the
user.html
template to display the new fields. - Modify the plugin's
admin
method to handle the new properties.
Example for adding a new "department" field:
- Add to UserForm:
# In forms/UserForm.py
department = StringField('Department')
- Store the property in the
op=add
andop=edit
handlers:
# In __init__.py admin method
obj.setProperty("department", form.department.data)
Sources: forms/UserForm.py:6-13, init.py:34-44, init.py:45-56
To add a new operation to the plugin:
- Add a new branch to the
op
conditional in theadmin
method. - Implement the operation logic.
- Create any necessary templates or modify existing ones.
Example structure for a new operation:
# In __init__.py admin method
elif op == 'new_operation':
# Handle GET request (display form)
if request.method == 'GET':
# Initialize form or prepare data
return self.render('template.html', context_data)
# Handle POST request (process form submission)
if form.validate_on_submit():
# Process form data
return redirect(self.name)
else:
# Re-render form with validation errors
return self.render('template.html', context_data)
Sources: init.py:31-87
The plugin uses Jinja2 templates with a consistent inheritance structure:
flowchart TD
A["layouts/module_admin.html"] --> B["templates/users.html"]
A --> C["templates/user.html"]
A --> D["templates/password.html"]
E["forms/UserForm.py"] --> C
F["forms/PasswordForm.py"] --> D
When modifying templates:
- Respect the block structure defined in the parent template.
- Use the
{{ form.field.label() }}
and{{ form.field() }}
pattern for form fields. - For AJAX operations, follow the pattern used in the image upload functionality.
Sources: templates/user.html:1-159
The plugin includes JavaScript for secure client-side API key generation. When implementing similar functionality:
- Use the native crypto API for secure random values.
- Apply appropriate formatting for readability.
function generateSecureKey() {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const crypto = window.crypto || window.msCrypto;
const randomValues = new Uint8Array(16);
crypto.getRandomValues(randomValues);
return Array.from(randomValues)
.map(v => chars[v % chars.length])
.join('')
.replace(/(.{4})/g, '$1-')
.slice(0, -1);
}
Sources: templates/user.html:141-157
For implementing file uploads:
- Use FormData to prepare the upload payload.
- Include appropriate error handling.
- Update UI elements after successful upload.
The plugin uses this pattern for avatar image uploads:
function uploadFile() {
var formData = new FormData($('#uploadForm')[0]);
formData.append('user', "{{ form.username.data}}");
$.ajax({
url: 'Users?op=upload_image',
type: 'POST',
data: formData,
contentType: false,
processData: false,
success: function(response) {
// Handle success
},
error: function(xhr, status, error) {
// Handle error
}
});
}
Sources: templates/user.html:118-140
The plugin uses a core application object system rather than direct database access. Understanding this pattern is crucial when extending the plugin.
sequenceDiagram
participant Plugin as "Users Plugin"
participant Core as "Core API"
participant Storage as "Object Storage"
Note over Plugin, Storage: Creating a user
Plugin->>Core: addObject(username, "Users")
Core->>Storage: Create object
Core-->>Plugin: Object reference
Plugin->>Core: obj.setProperty("field", value)
Core->>Storage: Update object
Note over Plugin, Storage: Getting user data
Plugin->>Core: getObject(username)
Core->>Storage: Retrieve object
Core-->>Plugin: Object reference
Plugin->>Core: obj.getProperty("field")
Core-->>Plugin: Property value
Note over Plugin, Storage: Updating user data
Plugin->>Core: obj.setProperty("field", value)
Core->>Storage: Update object
Key methods for working with the object system:
Method | Purpose | Example Usage |
---|---|---|
getObjectsByClass() |
Retrieve all objects of a class | users = getObjectsByClass("Users") |
getObject() |
Get a specific object by ID | obj = getObject(user_name) |
addObject() |
Create a new object | obj = addObject(form.username.data, "Users") |
setProperty() |
Set a property on an object | obj.setProperty("role", form.role.data) |
getProperty() |
Get a property from an object | obj.getProperty("lastLogin") |
deleteObject() |
Delete an object | deleteObject(user_name) |
Sources: init.py:31-94
When extending the plugin, keep these security best practices in mind:
- Input Validation: Always use WTForms validators to validate user input.
-
Password Handling: Never store plaintext passwords. The plugin uses the
set_password()
method from the User model. - File Uploads: Validate file types and sizes for uploaded images to prevent security vulnerabilities.
- API Keys: Generate strong, random API keys and validate them properly when used for authentication.
Sources: init.py:60-69, templates/user.html:146-157
When modifying the plugin:
- Fork the repository from https://github.com/Anisan/osysHome-Users
- Make your changes following the architecture patterns described in this guide
- Test your changes thoroughly
- Submit a pull request with a detailed description of your changes
Common issues when extending the plugin:
- Form validation errors: Check that all required fields have validators and data is correctly passed to the form.
- Template rendering issues: Ensure your templates extend the correct base template and use the proper block structure.
- Object access errors: Verify that object names are correct and the core API is being used properly.