class UserShow(admin_views.Show): """Show one user.""" resource_buttons = admin_views.Show.resource_buttons + [TraverseLinkButton(id="set-password", name="Set password", view_name="set-password")] includes = ["id", "uuid", "enabled", "created_at", "updated_at", "username", colander.SchemaNode(colander.String(), name='full_name'), "email", "last_login_at", "last_login_ip", colander.SchemaNode(colander.String(), name="registration_source", missing=colander.drop), colander.SchemaNode(colander.String(), name="social"), colander.SchemaNode(GroupSet(), name="groups", widget=defer_widget_values(deform.widget.CheckboxChoiceWidget, group_vocabulary, css_class="groups")) ] form_generator = SQLAlchemyFormGenerator(includes=includes) def get_title(self): return "{} #{}".format(self.get_object().friendly_name, self.get_object().id) @view_config(context=UserAdmin.Resource, route_name="admin", name="show", renderer="crud/show.html", permission='view') def show(self): return super(UserShow, self).show()
class UserAdd(admin_views.Add): """CRUD add part for creating new users.""" #: TODO: Not sure how we should manage with explicit username - it's not used for login so no need to have a point to ask includes = [ # "username", --- usernames are never exposed anymore colander.SchemaNode(colander.String(), name="email", validator=validate_unique_user_email), "full_name", colander.SchemaNode(colander.String(), name='password', widget=deform.widget.CheckedPasswordWidget(css_class="password-widget")), colander.SchemaNode(GroupSet(), name="groups", widget=defer_widget_values(deform.widget.CheckboxChoiceWidget, group_vocabulary, css_class="groups")) ] form_generator = SQLAlchemyFormGenerator(includes=includes) def get_form(self): # TODO: Still not sure how handle nested values on the automatically generated add form. But here we need it for groups to appear return self.create_form(EditMode.add, buttons=("add", "cancel",)) def initialize_object(self, form, appstruct, obj: User): password = appstruct.pop("password") form.schema.objectify(appstruct, obj) hasher = self.request.registry.getUtility(IPasswordHasher) obj.hashed_password = hasher.hash_password(password) # Users created through admin are useable right away, so activate the user obj.activated_at = now()
class UserEdit(admin_views.Edit): """Edit one user in admin interface.""" includes = [ "enabled", colander.SchemaNode(colander.String(), name='username'), # Make username required field colander.SchemaNode(colander.String(), name='full_name', missing=""), "email", colander.SchemaNode(GroupSet(), name="groups", widget=defer_widget_values(deform.widget.CheckboxChoiceWidget, group_vocabulary, css_class="groups")) ] form_generator = SQLAlchemyFormGenerator(includes=includes) def save_changes(self, form:deform.Form, appstruct:dict, user:User): """Save the user edit and reflect if we need to drop user session.""" enabled_changes = appstruct["enabled"] != user.enabled email_changes = appstruct["email"] != user.email username_changes = appstruct["username"] != user.username super(UserEdit, self).save_changes(form, appstruct, user) # Notify authentication system to drop all sessions for this user if enabled_changes: kill_user_sessions(self.request, user, "enabled_change") elif email_changes: kill_user_sessions(self.request, user, "email_change") elif username_changes: kill_user_sessions(self.request, user, "username_change") def get_title(self): return "{} #{}".format(self.get_object().friendly_name, self.get_object().id) @view_config(context=UserAdmin.Resource, route_name="admin", name="edit", renderer="crud/edit.html", permission='edit') def edit(self): return super(UserEdit, self).edit()
class UserAdd(admin_views.Add): """CRUD add part for creating new users.""" #: TODO: Not sure how we should manage with explicit username - it's not used for login so no need to have a point to ask includes = [ # "username", --- usernames are never exposed anymore colander.SchemaNode(colander.String(), name="email", validator=validate_unique_user_email), "full_name", colander.SchemaNode(colander.String(), name='password', widget=deform.widget.CheckedPasswordWidget(css_class="password-widget")), colander.SchemaNode(GroupSet(), name="groups", widget=defer_widget_values(deform.widget.CheckboxChoiceWidget, group_vocabulary, css_class="groups")) ] form_generator = SQLAlchemyFormGenerator(includes=includes) def get_form(self): # TODO: Still not sure how handle nested values on the automatically generated add form. But here we need it for groups to appear return self.create_form(EditMode.add, buttons=("add", "cancel",)) def add_object(self, obj): """Flush newly created object to persist storage.""" # Users created through admin are useable right away obj.activated_at = now() super(UserAdd, self).add_object(obj)
class AssetSchema(colander.Schema): #: Human readable name name = colander.SchemaNode(colander.String()) #: The network this asset is in network = colander.SchemaNode( UUIDForeignKeyValue(model=AssetNetwork, match_column="id"), widget=defer_widget_values(deform.widget.SelectWidget, available_networks), ) #: Symbol how this asset is presented in tickers symbol = colander.SchemaNode(colander.String()) description = colander.SchemaNode(colander.String(), missing="") #: Markdown page telling about this asset long_description = colander.SchemaNode(colander.String(), description="Markdown formatted", missing="", widget=deform.widget.TextAreaWidget( rows=20, cols=80)) #: Ethereum address external_id = colander.SchemaNode(colander.String(), title="Address", validator=validate_ethereum_address, missing=None, description="0x hex string format") #: Number of units avaialble supply = colander.SchemaNode(colander.Decimal(), missing=None) #: What kind of asset is this asset_class = colander.SchemaNode( EnumValue(AssetClass), widget=deform.widget.SelectWidget(values=enum_values(AssetClass))) #: Workflow state of this asset state = colander.SchemaNode( EnumValue(AssetState), widget=deform.widget.SelectWidget(values=enum_values(AssetState))) other_data = colander.SchemaNode( JSONValue(), widget=JSONWidget(), description="JSON bag of attributes of the object", missing=dict) def dictify(self, obj: Asset) -> dict: """Serialize SQLAlchemy model instance to nested dictionary appstruct presentation.""" appstruct = dictify(self, obj, excludes=("long_description", "external_id")) # Convert between binary storage and human readable hex presentation appstruct["long_description"] = obj.other_data.pop( "long_description", "") if obj.external_id: appstruct["external_id"] = bin_to_eth_address(obj.external_id) else: appstruct["external_id"] = "" return appstruct def objectify(self, appstruct: dict, obj: Asset): """Store the dictionary data from the form submission on the object.""" objectify(self, appstruct, obj, excludes=("long_description", "external_id")) if not obj.other_data: # When creating the object JSON value may be None # instead of empty dict obj.other_data = {} # Special case of field stored inside JSON bag obj.other_data["long_description"] = appstruct["long_description"] # Convert between binary storage and human readable hex presentation if appstruct["external_id"]: obj.external_id = eth_address_to_bin(appstruct["external_id"])