Coverage for .tox / coverage / lib / python3.11 / site-packages / wuttaweb / views / people.py: 100%
84 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-20 21:14 -0500
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-20 21:14 -0500
1# -*- coding: utf-8; -*-
2################################################################################
3#
4# wuttaweb -- Web App for Wutta Framework
5# Copyright © 2024-2026 Lance Edgar
6#
7# This file is part of Wutta Framework.
8#
9# Wutta Framework is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License as published by the Free
11# Software Foundation, either version 3 of the License, or (at your option) any
12# later version.
13#
14# Wutta Framework is distributed in the hope that it will be useful, but
15# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17# more details.
18#
19# You should have received a copy of the GNU General Public License along with
20# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
21#
22################################################################################
23"""
24Views for people
25"""
27import sqlalchemy as sa
29from wuttjamaican.db.model import Person
30from wuttaweb.views import MasterView
31from wuttaweb.util import make_users_grid
34class PersonView(MasterView): # pylint: disable=abstract-method
35 """
36 Master view for people.
38 Default route prefix is ``people``.
40 Notable URLs provided by this class:
42 * ``/people/``
43 * ``/people/new``
44 * ``/people/XXX``
45 * ``/people/XXX/edit``
46 * ``/people/XXX/delete``
47 """
49 model_class = Person
50 model_title_plural = "People"
51 route_prefix = "people"
52 sort_defaults = "full_name"
53 has_autocomplete = True
55 grid_columns = [
56 "full_name",
57 "first_name",
58 "middle_name",
59 "last_name",
60 ]
62 filter_defaults = {
63 "full_name": {"active": True},
64 }
66 form_fields = [
67 "full_name",
68 "first_name",
69 "middle_name",
70 "last_name",
71 "users",
72 ]
74 mergeable = True
75 merge_additive_fields = ["usernames"]
77 def configure_grid(self, grid): # pylint: disable=empty-docstring
78 """ """
79 g = grid
80 super().configure_grid(g)
82 # full_name
83 g.set_link("full_name")
85 # first_name
86 g.set_link("first_name")
88 # last_name
89 g.set_link("last_name")
91 def configure_form(self, form): # pylint: disable=empty-docstring
92 """ """
93 f = form
94 super().configure_form(f)
95 person = f.model_instance
97 # full_name
98 if self.creating or self.editing:
99 f.remove("full_name")
101 # users
102 if self.creating or self.editing:
103 f.remove("users")
104 else:
105 f.set_grid("users", self.make_users_grid(person))
107 def make_users_grid(self, person):
108 """
109 Make and return the grid for the Users field.
111 This grid is shown for the Users field when viewing a Person.
113 :returns: Fully configured :class:`~wuttaweb.grids.base.Grid`
114 instance.
115 """
116 return make_users_grid(
117 self.request,
118 route_prefix=self.get_route_prefix(),
119 data=person.users,
120 columns=[
121 "username",
122 "active",
123 ],
124 )
126 def objectify(self, form): # pylint: disable=empty-docstring
127 """ """
128 person = super().objectify(form)
130 # full_name
131 person.full_name = self.app.make_full_name(person.first_name, person.last_name)
133 return person
135 def merge_get_data(self, obj): # pylint: disable=empty-docstring
136 """ """
137 person = obj
138 data = super().merge_get_data(person)
140 data["usernames"] = sorted([u.username for u in person.users])
142 return data
144 def merge_execute(self, removing, keeping):
145 """
146 We override default merge logic to re-assign users if needed.
148 See also parent method:
149 :meth:`~wuttaweb.views.master.MasterView.merge_execute()`
150 """
151 session = self.Session()
153 # reassign users
154 for user in list(removing.users):
155 user.person = keeping
156 session.flush()
158 # continue default merge
159 super().merge_execute(removing, keeping)
161 def autocomplete_query(self, term): # pylint: disable=empty-docstring
162 """ """
163 model = self.app.model
164 session = self.Session()
165 query = session.query(model.Person)
166 criteria = [model.Person.full_name.ilike(f"%{word}%") for word in term.split()]
167 query = query.filter(sa.and_(*criteria)).order_by(model.Person.full_name)
168 return query
170 def view_profile(self, session=None): # pylint: disable=empty-docstring
171 """ """
172 person = self.get_instance(session=session)
173 context = {
174 "person": person,
175 "instance": person,
176 }
177 return self.render_to_response("view_profile", context)
179 def make_user(self): # pylint: disable=empty-docstring
180 """ """
181 self.request.session.flash("TODO: this feature is not yet supported", "error")
182 return self.redirect(self.request.get_referrer())
184 @classmethod
185 def defaults(cls, config): # pylint: disable=empty-docstring
186 """ """
188 # nb. Person may come from custom model
189 wutta_config = config.registry.settings["wutta_config"]
190 app = wutta_config.get_app()
191 cls.model_class = app.model.Person
193 cls._defaults(config)
194 cls._people_defaults(config)
196 @classmethod
197 def _people_defaults(cls, config):
198 route_prefix = cls.get_route_prefix()
199 url_prefix = cls.get_url_prefix()
200 instance_url_prefix = cls.get_instance_url_prefix()
201 permission_prefix = cls.get_permission_prefix()
203 # view profile
204 config.add_route(
205 f"{route_prefix}.view_profile",
206 f"{instance_url_prefix}/profile",
207 request_method="GET",
208 )
209 config.add_view(
210 cls,
211 attr="view_profile",
212 route_name=f"{route_prefix}.view_profile",
213 permission=f"{permission_prefix}.view_profile",
214 )
216 # make user for person
217 config.add_route(
218 f"{route_prefix}.make_user",
219 f"{url_prefix}/make-user",
220 request_method="POST",
221 )
222 config.add_view(
223 cls,
224 attr="make_user",
225 route_name=f"{route_prefix}.make_user",
226 permission="users.create",
227 )
230def defaults(config, **kwargs): # pylint: disable=missing-function-docstring
231 base = globals()
233 PersonView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name
234 "PersonView", base["PersonView"]
235 )
236 PersonView.defaults(config)
239def includeme(config): # pylint: disable=missing-function-docstring
240 defaults(config)