fairdm.core.utils#

Helpers for core records: polymorphic-aware permissions and fieldsets.

Module Contents#

Functions#

get_non_polymorphic_instance

Return an object re-fetched through its polymorphic base’s non-polymorphic manager.

get_permission_target

Return the object a permission check or grant should actually target.

assign_perm

Assign a permission, normalising a polymorphic instance first.

remove_perm

Remove a permission, with the same normalisation as :func:assign_perm.

get_perms

List every permission a user or group holds on an object.

get_objects_for_user

List the objects a user holds a permission for, allowing for polymorphic subclasses.

model_class_inheritance_to_fieldsets

Group a sample model’s own fields into fieldsets, one per class in its inheritance chain.

Data#

UUID_RE_PATTERN

A regex the matches the uuid of a core data object (project, sample, measurement, etc.) and captures it in a named group ‘uuid’.

CORE_PERMISSIONS

API#

UUID_RE_PATTERN#

‘^(?P[pdsmea-zA-Z0-9_-]{22})/$’

A regex the matches the uuid of a core data object (project, sample, measurement, etc.) and captures it in a named group ‘uuid’.

CORE_PERMISSIONS#

[(‘add_contributor’, ‘Can add contributors’), (‘modify_contributor’, ‘Can modify contributors’), (‘m…

get_non_polymorphic_instance(obj)[source]#

Return an object re-fetched through its polymorphic base’s non-polymorphic manager.

Gated on type_of directly, not on polymorphic_model_marker: every polymorphic model carries the marker, but only Sample, Measurement and Contributor declare type_of. A portal-defined polymorphic model that is none of those would otherwise raise AttributeError here rather than being left alone.

Args: obj: The record to re-fetch.

Returns: The non-polymorphic instance, or obj itself when it declares no type_of.

get_permission_target(obj, perm)[source]#

Return the object a permission check or grant should actually target.

A polymorphic subclass instance (e.g. RockSample) carries its own app label and content type, so guardian either raises WrongAppError or silently misses a stored row when the permission is declared on the polymorphic base instead. Normalising to the base fixes that, but only when the base is the one that actually owns the permission being checked. Doing it unconditionally would retarget every polymorphic record’s content type, including one whose own subclass owns the permission: an Organization normalised to Contributor would orphan every permission ever assigned to it.

Gated on the object, not on the permission string: guardian only compares app labels when the permission carries one ("." in perm), so a gate keyed on that would never fire for an unqualified permission and the failure would become a silent denial instead of an error.

Gated on type_of directly, not on polymorphic_model_marker: a portal-defined polymorphic model that declares no type_of would otherwise raise AttributeError inside an authentication backend rather than being left alone.

Args: obj: The record the permission is checked or granted on, or None. perm: The permission codename, with or without an app label.

Returns: The object to pass to guardian: obj itself or its non-polymorphic base instance.

assign_perm(perm, user_or_group, obj)[source]#

Assign a permission, normalising a polymorphic instance first.

A backend takes no part in granting a right. guardian.shortcuts.assign_perm resolves the object’s own content type directly, so a permission declared on a polymorphic base (e.g. change_sample) could not be stored against a subclass instance. Uses the same gate as the check side, so the two never disagree about which records they cover.

Args: perm: The permission to assign. user_or_group: The user or group receiving the permission. obj: The record the permission applies to.

Returns: Whatever guardian.shortcuts.assign_perm returns.

remove_perm(perm, user_or_group, obj)[source]#

Remove a permission, with the same normalisation as :func:assign_perm.

Args: perm: The permission to remove. user_or_group: The user or group losing the permission. obj: The record the permission applies to.

Returns: Whatever guardian.shortcuts.remove_perm returns.

get_perms(user_or_group, obj)[source]#

List every permission a user or group holds on an object.

Merges rows stored against the object’s own content type with rows stored against its polymorphic base, because :func:assign_perm may have written to either depending on which one owns the permission, and there is no single permission here to gate the choice on.

Args: user_or_group: The user or group to look up. obj: The record to look up permissions on.

Returns: The sorted permission codenames.

get_objects_for_user(user, perm, klass, **kwargs)[source]#

List the objects a user holds a permission for, allowing for polymorphic subclasses.

guardian.shortcuts.get_objects_for_user derives its content-type filter from the permission’s own app label and model name, so a permission built from a subclass (e.g. "demo.view_rocksample") finds nothing when the grant is filed under the polymorphic base’s content type (sample.view_sample). Handing it the base-model permission alongside a subclass queryset raises MixedContentTypeError. This recomputes the permission against the base model, resolves matching primary keys there, and narrows the caller’s own queryset by them. That is safe because a polymorphic subclass shares its primary key with its base.

Args: user: The user to look up. perm: The permission, in app_label.codename form. klass: The model or queryset to filter. **kwargs: Keyword arguments passed to guardian.shortcuts.get_objects_for_user.

Returns: The queryset of objects the user holds the permission for.

model_class_inheritance_to_fieldsets(obj_or_class)[source]#

Group a sample model’s own fields into fieldsets, one per class in its inheritance chain.

Each field appears once, under the first class in the chain that declares it, and the bookkeeping fields every sample carries (id, local_id, image and the like) are left out. The most derived class’s fields are then folded into the first group, and that last group is dropped.

Args: obj_or_class: A sample instance or sample model class.

Returns: A list of (name, {"fields": [...]}) pairs. name is the class’s verbose name, or None for Sample itself.