Coverage for gws-app/gws/plugin/model_field/related_feature/__init__.py: 83%
65 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-16 22:59 +0200
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-16 22:59 +0200
1"""Related Feature field
3Represents a child->parent M:1 relationship to another model::
5 +-------------+ +--------------+
6 | model | | toModel |
7 +-------------+ +--------------+
8 | fromKey |-------->| toKey |
9 +-------------+ +--------------+
11The value of the field is the parent feature.
12"""
14import gws
15import gws.base.model
16import gws.base.model.related_field as related_field
17import gws.lib.sa as sa
19gws.ext.new.modelField('relatedFeature')
22class Config(related_field.Config):
23 """Configuration for related feature field."""
25 fromColumn: str
26 """Foreign key column in this table."""
27 toModel: str
28 """Related model."""
29 toColumn: str = ''
30 """Key column in the related model, primary key by default."""
33class Props(related_field.Props):
34 pass
37class Object(related_field.Object):
38 attributeType = gws.AttributeType.feature
40 def configure_relationship(self):
41 to_mod = self.get_model(self.cfg('toModel'))
43 self.rel = related_field.Relationship(
44 src=related_field.RelRef(
45 model=self.model,
46 table=self.model.table(),
47 key=self.model.column(self.cfg('fromColumn')),
48 uid=self.model.uid_column(),
49 ),
50 tos=[
51 related_field.RelRef(
52 model=to_mod,
53 table=to_mod.table(),
54 key=self.column_or_uid(to_mod, self.cfg('toColumn')),
55 uid=to_mod.uid_column(),
56 )
57 ],
58 )
59 self.rel.to = self.rel.tos[0]
61 ##
63 def do_init(self, feature, mc):
64 key = feature.record.attributes.get(self.rel.src.key.name)
65 if key:
66 to_uids = self.uids_for_key(self.rel.to, key, mc)
67 to_features = self.rel.to.model.get_features(
68 to_uids,
69 gws.base.model.secondary_context(mc),
70 )
71 if to_features:
72 feature.attributes[self.name] = to_features[0]
74 def after_create_related(self, to_feature, mc):
75 if to_feature.model != self.rel.to.model:
76 return
78 for feature in to_feature.createWithFeatures:
79 if feature.model == self.model:
80 key = self.key_for_uid(
81 self.rel.to.model,
82 self.rel.to.key,
83 to_feature.insertedPrimaryKey,
84 mc,
85 )
86 if key:
87 self.update_key_for_uids(
88 self.model,
89 self.rel.src.key,
90 [feature.uid()],
91 key,
92 mc,
93 )
95 def uids_for_key(self, rel: related_field.RelRef, key, mc):
96 sql = sa.select(rel.uid).where(rel.key.__eq__(key))
97 with rel.model.db.connect() as conn:
98 return set(str(u) for u in conn.execute(sql))
100 def after_select(self, features, mc):
101 if not mc.user.can_read(self) or mc.relDepth >= mc.maxDepth:
102 return
104 uid_to_f = {f.uid(): f for f in features}
106 sql = (
107 sa.select(
108 self.rel.to.uid,
109 self.rel.src.uid,
110 )
111 .select_from(
112 self.rel.to.table.join(
113 self.rel.src.table,
114 self.rel.src.key.__eq__(self.rel.to.key),
115 ),
116 )
117 .where(self.rel.src.uid.in_(uid_to_f))
118 )
120 r_to_uids = {}
121 with self.model.db.connect() as conn:
122 for r, u in conn.execute(sql):
123 r_to_uids.setdefault(str(r), []).append(str(u))
125 for to_feature in self.rel.to.model.get_features(
126 r_to_uids,
127 gws.base.model.secondary_context(mc),
128 ):
129 for uid in r_to_uids.get(to_feature.uid(), []):
130 feature = uid_to_f.get(uid)
131 feature.set(self.name, to_feature)
133 def before_create(self, feature, mc):
134 self.before_write(feature, mc)
136 def before_update(self, feature, mc):
137 self.before_write(feature, mc)
139 def before_write(self, feature: gws.Feature, mc: gws.ModelContext):
140 if not mc.user.can_write(self):
141 return
143 if feature.has(self.name):
144 key = None
145 to_feature = feature.get(self.name)
146 if to_feature:
147 key = self.key_for_uid(
148 self.rel.to.model,
149 self.rel.to.key,
150 to_feature.uid(),
151 mc,
152 )
153 feature.record.attributes[self.rel.src.key.name] = key