1+ from django .core .exceptions import ValidationError
12from django .db import transaction
23
34from drf_spectacular .openapi import OpenApiParameter , OpenApiTypes
45from drf_spectacular .utils import extend_schema
5- from rest_framework .fields import empty
66from rest_framework .parsers import MultiPartParser
77from rest_framework .permissions import AllowAny
88from rest_framework .request import Request
1515from baserow .api .user_files .serializers import UserFileSerializer
1616from baserow .api .utils import validate_data
1717from baserow .contrib .database .api .fields .errors import ERROR_FIELD_DATA_CONSTRAINT
18- from baserow .contrib .database .api .rows .errors import ERROR_CANNOT_CREATE_ROWS_IN_TABLE
18+ from baserow .contrib .database .api .rows .errors import (
19+ ERROR_CANNOT_CREATE_ROWS_IN_TABLE ,
20+ ERROR_ROW_DOES_NOT_EXIST ,
21+ )
1922from baserow .contrib .database .api .rows .serializers import (
2023 get_example_row_serializer_class ,
2124 get_row_serializer_class ,
2629)
2730from baserow .contrib .database .api .views .utils import get_public_view_authorization_token
2831from baserow .contrib .database .fields .exceptions import FieldDataConstraintException
29- from baserow .contrib .database .fields .models import FileField , LongTextField
30- from baserow .contrib .database .rows .exceptions import CannotCreateRowsInTable
31- from baserow .contrib .database .views .actions import SubmitFormActionType
32+ from baserow .contrib .database .fields .models import (
33+ FileField ,
34+ LongTextField ,
35+ )
36+ from baserow .contrib .database .rows .exceptions import (
37+ CannotCreateRowsInTable ,
38+ RowDoesNotExist ,
39+ )
40+ from baserow .contrib .database .views .actions import (
41+ EditFormRowActionType ,
42+ SubmitFormActionType ,
43+ )
3244from baserow .contrib .database .views .exceptions import (
3345 NoAuthorizationToPubliclySharedView ,
3446 ViewDoesNotExist ,
3547)
3648from baserow .contrib .database .views .handler import ViewHandler
3749from baserow .contrib .database .views .models import FormView
3850from baserow .contrib .database .views .registries import view_ownership_type_registry
39- from baserow .contrib .database .views .validators import (
40- allow_only_specific_select_options_factory ,
41- no_empty_form_values_when_required_validator ,
42- )
4351from baserow .core .action .registries import action_type_registry
4452from baserow .core .user_files .exceptions import (
4553 FileSizeTooLargeError ,
4957
5058from .errors import (
5159 ERROR_FORM_DOES_NOT_EXIST ,
60+ ERROR_INVALID_EDIT_ROW_TOKEN ,
5261 ERROR_NO_PERMISSION_TO_PUBLICLY_SHARED_FORM ,
5362 ERROR_VIEW_HAS_NO_PUBLIC_FILE_FIELD ,
5463)
55- from .exceptions import ViewHasNoPublicFileFieldError
64+ from .exceptions import (
65+ InvalidEditRowTokenError ,
66+ ViewHasNoPublicFileFieldError ,
67+ )
5668from .serializers import FormViewSubmittedSerializer , PublicFormViewSerializer
69+ from .utils import build_field_kwargs_for_options , decode_and_validate_edit_token
5770
5871
5972class SubmitFormViewView (APIView ):
@@ -146,27 +159,9 @@ def post(self, request: Request, slug: str) -> Response:
146159 model = form .table .get_model ()
147160
148161 options = form .active_field_options
149- field_kwargs = {}
150- for option in options :
151- validators = []
152- o = {}
153- if option .is_required ():
154- o ["required" ] = True
155- o ["default" ] = empty
156- validators .append (no_empty_form_values_when_required_validator )
157- if not option .include_all_select_options :
158- validators .append (
159- allow_only_specific_select_options_factory (
160- [
161- allowed_select_option .id
162- for allowed_select_option in option .allowed_select_options .all ()
163- ]
164- )
165- )
166- if len (validators ) > 0 and len (o ) > 0 :
167- name = model ._field_objects [option .field_id ]["name" ]
168- o ["validators" ] = validators
169- field_kwargs [name ] = o
162+ field_kwargs = build_field_kwargs_for_options (
163+ model , options , enforce_required = True
164+ )
170165
171166 field_ids = [option .field_id for option in options ]
172167 validation_serializer = get_row_serializer_class (
@@ -183,6 +178,173 @@ def post(self, request: Request, slug: str) -> Response:
183178 return Response (FormViewSubmittedSerializer (form ).data )
184179
185180
181+ class EditRowFormViewView (APIView ):
182+ permission_classes = (AllowAny ,)
183+
184+ @extend_schema (
185+ parameters = [
186+ OpenApiParameter (
187+ name = "slug" ,
188+ location = OpenApiParameter .PATH ,
189+ type = OpenApiTypes .STR ,
190+ required = True ,
191+ description = "The slug of the form view." ,
192+ ),
193+ OpenApiParameter (
194+ name = "row_token" ,
195+ location = OpenApiParameter .PATH ,
196+ type = OpenApiTypes .STR ,
197+ required = True ,
198+ description = "The signed edit token that identifies the row to edit." ,
199+ ),
200+ ],
201+ tags = ["Database table form view" ],
202+ operation_id = "get_edit_row_database_table_form_view" ,
203+ description = (
204+ "Returns the current field values of the row identified by the edit token. "
205+ "Only fields visible in the form view are returned. The token must be a "
206+ "valid signed token generated by a form_view_edit_row field."
207+ ),
208+ responses = {
209+ 200 : get_example_row_serializer_class (example_type = "get" ),
210+ 401 : get_error_schema (
211+ [
212+ "ERROR_NO_PERMISSION_TO_PUBLICLY_SHARED_FORM" ,
213+ ]
214+ ),
215+ 404 : get_error_schema (
216+ [
217+ "ERROR_FORM_DOES_NOT_EXIST" ,
218+ "ERROR_ROW_DOES_NOT_EXIST" ,
219+ "ERROR_INVALID_EDIT_ROW_TOKEN" ,
220+ ]
221+ ),
222+ },
223+ )
224+ @map_exceptions (
225+ {
226+ ViewDoesNotExist : ERROR_FORM_DOES_NOT_EXIST ,
227+ NoAuthorizationToPubliclySharedView : ERROR_NO_PERMISSION_TO_PUBLICLY_SHARED_FORM ,
228+ InvalidEditRowTokenError : ERROR_INVALID_EDIT_ROW_TOKEN ,
229+ RowDoesNotExist : ERROR_ROW_DOES_NOT_EXIST ,
230+ }
231+ )
232+ def get (self , request : Request , slug : str , row_token : str ) -> Response :
233+ handler = ViewHandler ()
234+ form = handler .get_public_view_by_slug (
235+ request .user ,
236+ slug ,
237+ view_model = FormView ,
238+ authorization_token = get_public_view_authorization_token (request ),
239+ )
240+ cell_uuid , field_id = decode_and_validate_edit_token (form , row_token )
241+
242+ model = form .table .get_model ()
243+ field_column = f"field_{ field_id } "
244+
245+ try :
246+ row = model .objects .get (** {field_column : cell_uuid })
247+ except (model .DoesNotExist , ValidationError ):
248+ raise RowDoesNotExist (cell_uuid )
249+
250+ options = form .active_field_options
251+ field_ids = [option .field_id for option in options ]
252+
253+ serializer_class = get_row_serializer_class (
254+ model , is_response = True , field_ids = field_ids
255+ )
256+ return Response (serializer_class (row ).data )
257+
258+ @extend_schema (
259+ parameters = [
260+ OpenApiParameter (
261+ name = "slug" ,
262+ location = OpenApiParameter .PATH ,
263+ type = OpenApiTypes .STR ,
264+ required = True ,
265+ description = "The slug of the form view." ,
266+ ),
267+ OpenApiParameter (
268+ name = "row_token" ,
269+ location = OpenApiParameter .PATH ,
270+ type = OpenApiTypes .STR ,
271+ required = True ,
272+ description = "The signed edit token that identifies the row to edit." ,
273+ ),
274+ ],
275+ tags = ["Database table form view" ],
276+ operation_id = "update_edit_row_database_table_form_view" ,
277+ description = (
278+ "Updates the row identified by the edit token using the submitted field "
279+ "values. Only fields that are visible in the form view can be changed. "
280+ "The `row_token` must be a valid signed token generated by a "
281+ "form_view_edit_row field."
282+ ),
283+ request = get_example_row_serializer_class (example_type = "patch" ),
284+ responses = {
285+ 200 : FormViewSubmittedSerializer ,
286+ 401 : get_error_schema (
287+ [
288+ "ERROR_NO_PERMISSION_TO_PUBLICLY_SHARED_FORM" ,
289+ ]
290+ ),
291+ 400 : get_error_schema (["ERROR_FIELD_DATA_CONSTRAINT" ]),
292+ 404 : get_error_schema (
293+ [
294+ "ERROR_FORM_DOES_NOT_EXIST" ,
295+ "ERROR_ROW_DOES_NOT_EXIST" ,
296+ "ERROR_INVALID_EDIT_ROW_TOKEN" ,
297+ ]
298+ ),
299+ },
300+ )
301+ @map_exceptions (
302+ {
303+ ViewDoesNotExist : ERROR_FORM_DOES_NOT_EXIST ,
304+ NoAuthorizationToPubliclySharedView : ERROR_NO_PERMISSION_TO_PUBLICLY_SHARED_FORM ,
305+ InvalidEditRowTokenError : ERROR_INVALID_EDIT_ROW_TOKEN ,
306+ RowDoesNotExist : ERROR_ROW_DOES_NOT_EXIST ,
307+ FieldDataConstraintException : ERROR_FIELD_DATA_CONSTRAINT ,
308+ }
309+ )
310+ @transaction .atomic
311+ def patch (self , request : Request , slug : str , row_token : str ) -> Response :
312+ handler = ViewHandler ()
313+ form = handler .get_public_view_by_slug (
314+ request .user ,
315+ slug ,
316+ view_model = FormView ,
317+ authorization_token = get_public_view_authorization_token (request ),
318+ )
319+
320+ cell_uuid , field_id = decode_and_validate_edit_token (form , row_token )
321+ data = request .data
322+
323+ model = form .table .get_model ()
324+ field_column = f"field_{ field_id } "
325+
326+ try :
327+ row = model .objects .get (** {field_column : cell_uuid })
328+ except (model .DoesNotExist , ValidationError ):
329+ raise RowDoesNotExist (cell_uuid )
330+
331+ options = form .active_field_options
332+ field_kwargs = build_field_kwargs_for_options (model , options )
333+
334+ field_ids = [option .field_id for option in options ]
335+ validation_serializer = get_row_serializer_class (
336+ model , field_ids = field_ids , field_kwargs = field_kwargs
337+ )
338+ values = validate_data (validation_serializer , data , return_validated = True )
339+
340+ updated_row = action_type_registry .get_by_type (EditFormRowActionType ).do (
341+ request .user , form , row .id , values , model , options
342+ )
343+
344+ form .row_id = updated_row .id
345+ return Response (FormViewSubmittedSerializer (form ).data )
346+
347+
186348class FormUploadFileView (APIView ):
187349 permission_classes = (AllowAny ,)
188350 parser_classes = (MultiPartParser ,)
0 commit comments