class FilterSerializer(serializers.ModelSerializer): template_context = dict(url_reverse='filter') form_titles = { 'table': 'Dynamic filter list', 'new': 'New object', 'edit': 'Editing object', } actions = Actions( TableAction(TablePosition.FILTER_ROW_END, _('+ Add'), title=_('Add new record'), name='add', action_js="dynamicforms.newRow('{% url url_reverse|add:'-detail' pk='new' format='html' %}'" ", 'record', __TABLEID__);"), TableAction(TablePosition.ROW_CLICK, _('Edit'), title=_('Edit record'), name='edit', action_js="dynamicforms.editRow('{% url url_reverse|add:'-detail' pk='__ROWID__' " "format='html' %}'.replace('__ROWID__', $(event.target.parentElement).closest('tr[class=\"df-table-row\"]').attr('data-id')), 'record', __TABLEID__);"), TableAction(TablePosition.ROW_END, label=_('Delete'), title=_('Delete record'), name='delete', action_js="dynamicforms.deleteRow('{% url url_reverse|add:'-detail' pk=row.id %}', " "{{row.id}}, 'record', __TABLEID__);"), TableAction(TablePosition.FILTER_ROW_END, label=_('Filter'), title=_('Filter'), name='filter', action_js="dynamicforms.defaultFilter(event);") ) show_filter = True name = NameTestField( label='Name field', max_length=list(filter(lambda f: f.name == 'name', Filter._meta.fields))[0].max_length, allow_null=list(filter(lambda f: f.name == 'name', Filter._meta.fields))[0].null, source='*', ) class Meta: model = Filter exclude = ()
class PageLoadSerializer(serializers.ModelSerializer): form_titles = { 'table': 'Dynamic page loader list', 'new': 'New object', 'edit': 'Editing object', } actions = Actions(add_default_crud=True, add_default_filter=True) show_filter = True class Meta: model = PageLoad exclude = ()
class HiddenFieldsSerializer(serializers.ModelSerializer): form_titles = { 'table': 'Hidden fields list', 'new': 'New hidden fields object', 'edit': 'Editing hidden fields object', } actions = Actions( FieldChangeAction(['note'], 'examples.action_hiddenfields_note'), FieldChangeAction(['unit'], 'examples.action_hiddenfields_unit'), FormInitAction( 'examples.hide_fields_on_show("{{ serializer.uuid }}");'), add_default_crud=True, add_default_filter=False) class Meta: model = HiddenFields exclude = ()
class SingleDialogSerializer(serializers.Serializer): form_titles = { 'table': '', 'new': 'Choose a value', 'edit': '', } form_template = 'examples/single_dialog.html' test = fields.ChoiceField(label='What should we say?', choices=( ('Today is sunny', 'Today is sunny'), ('Never-ending rain', 'Never-ending rain') )) actions = Actions( FormButtonAction(FormButtonTypes.CANCEL), FormButtonAction(FormButtonTypes.CUSTOM, label='Download it', action_js="customSingleDialogBtnPost();"), FormButtonAction(FormButtonTypes.CUSTOM, label='Say it', button_is_primary=True, action_js="customSingleDialogBtn();"), add_form_buttons=False )
class BasicFieldsSerializer(serializers.ModelSerializer): form_titles = { 'table': 'Basic fields list', 'new': 'New basic fields object', 'edit': 'Editing basic fields object', } form_template = 'examples/form_cols.html' actions = Actions(TableAction(TablePosition.HEADER, _('Modal dialog'), title=_('Dialog test'), name='modal_dialog', action_js="examples.testModalDialog();"), add_default_crud=True, add_form_buttons=True) boolean_field = fields.BooleanField() nullboolean_field = fields.NullBooleanField() char_field = fields.CharField() email_field = fields.EmailField() slug_field = fields.SlugField() url_field = fields.URLField() uuid_field = fields.UUIDField() ipaddress_field = fields.IPAddressField() integer_field = fields.IntegerField() nullint_field = fields.IntegerField(allow_null=True) float_field = fields.FloatField() decimal_field = fields.DecimalField(max_digits=5, decimal_places=2) datetime_field = fields.DateTimeField(required=False) date_field = fields.DateField() time_field = fields.TimeField() duration_field = fields.DurationField() password_field = fields.CharField(password_field=True) class Meta: model = BasicFields exclude = ()
class DynamicFormsSerializer(RenderMixin, ActionMixin): template_context = {} # see ViewSet.template_context template_name = DYNAMICFORMS.form_base_template #: template filename for single record view (HTMLFormRenderer) actions = Actions(add_default_crud=True, add_form_buttons=True) form_titles = { 'table': '', 'new': '', 'edit': '', } show_filter = False # When true, filter row is shown for list view def __init__(self, *args, is_filter: bool = False, **kwds): self.master = None self.is_filter = is_filter if self.is_filter: try: instance = self.Meta.model() for fld in instance._meta.fields: setattr(instance, fld.name, None) except: instance = StructDefault(_default_=None) kwds.setdefault('instance', instance) super().__init__(*args, **kwds) try: # hide the primary key field (DRF only marks it as R/O) field_name = self.Meta.model._meta.pk.name if field_name not in self._declared_fields: pk_field = self.fields[field_name] pk_field.display_form = fields.DisplayMode.HIDDEN pk_field.display_table = fields.DisplayMode.FULL except: pass if self.is_filter: for field in self.fields.values(): field.default = None field.allow_blank = True field.allow_null = True field.read_only = False field.display_form = field.display_table # filter's form is same as non-filter's table field.allow_tags = False field.password_field = False @property def has_non_field_errors(self): """ Reports whether validation turned up any form-wide validation errors. Used in templates to render the form-wide error message :return: True | False depending on whether form validation failed """ if hasattr(self, '_errors'): return 'non_field_errors' in self.errors return False @property def page_title(self): """ Returns page title from form_titles based on the rendered data :return string: page title """ if self.render_type == 'table': return self.form_titles.get('table', '') elif self.data.get('id', None): return self.form_titles.get('edit', '') else: return self.form_titles.get('new', '') # noinspection PyProtectedMember @property def filter_data(self): """ Returns serializer for filter row in table :return: Serializer """ if getattr(self, '_filter_ser', None) is None: # noinspection PyAttributeOutsideInit self._filter_ser = type(self)(is_filter=True, context=getattr(self, 'context', {})) self._filter_ser.master = self return self._filter_ser # Just create the same serializer in filter mode (None values, allow_nulls) # noinspection PyUnusedLocal def suppress_action(self, action, request, viewset): """ Determines whether rendering an action into the DOM should be suppressed. Use when some users don't have access to some of the functionality, e.g. when CRUD functionality is only enabled for administrative users :param action: action to be checked :param request: request that triggered the render (may be None) :param viewset: viewset that provided the serialized data (may be None) :return: boolean whether action should render (False) or not (True) """ return False @property def renderable_actions(self: 'serializers.Serializer'): """ Returns those actions that are not suppressed :return: List[Action] """ # TODO: Ta funkcija po mojem mora odletet (self.*controls*.actions). Sam zakaj ne? Ali se sploh ne uporablja? request = self.context.get('request', None) viewset = self.context.get('view', None) return [ action for action in self.controls.actions if not self.suppress_action(action, request, viewset) ] # noinspection PyUnresolvedReferences def get_initial(self) -> Any: if getattr(self, '_errors', None): # This basically reproduces BaseSerializer.data property except that it disregards the _errors member if self.instance: res = self.to_representation(self.instance) # elif hasattr(self, '_validated_data'): # res = self.to_representation(self.validated_data) else: res = {} res.update(super().get_initial()) return res return super().get_initial() # noinspection PyUnresolvedReferences @property def _writable_fields(self): """ Overrides DRF.serializers.Serializer._writable_fields This one in particular should return exactly the same list as DRF's version (as of 17.4.2020, DRF version 3.11 """ return (field for field in self.fields.values() if not field.read_only) # noinspection PyUnresolvedReferences @property def _readable_fields(self): """ Overrides DRF.serializers.Serializer._readable_fields This one adds additional checks on top of DRF's ones - checking if the field is renderable to table or form """ return (field for field in self.fields.values() if not (field.write_only or (self.is_rendering_to_list and self.display_table == DisplayMode.SUPPRESS) or (not self.is_rendering_to_list and self.display_form == DisplayMode.SUPPRESS))) def to_representation(self, instance, row_data=None): """ Object instance -> Dict of primitive datatypes. """ ret = OrderedDict() for field in self._readable_fields: try: attribute = field.get_attribute(instance) if not isinstance(field, RenderMixin): ret[field.field_name] = field.to_representation(attribute) else: try: ret[field.field_name] = field.to_representation( attribute, instance) except: if attribute is None and not field.required: # DRF makes a special check for none and then always returns None if the check is None. # We still want to process custom field to_representation, even if value is None. # But when field is a sub-serializer and it (it's FK reference) is none, it's OK # to pass None as result of serialization of such field raise SkipField() else: raise except SkipField: pass return ret
class UserSerializer(serializers.ModelSerializer): form_template = 'usereditor/user_item.html' form_titles = { 'table': _('Users'), 'new': _('New user'), 'edit': _('Editing user'), } actions = Actions(add_default_crud=True, add_default_filter=True) show_filter = True password = fields.CharField(label=_('Password'), write_only=True, display_table=fields.DisplayMode.SUPPRESS) full_name = fields.SerializerMethodField(label=_('Full name'), read_only=True, display_form=fields.DisplayMode.SUPPRESS) username = fields.CharField(label=_('Username'), display_table=fields.DisplayMode.SUPPRESS) email = UserEmailField(label=_('Email'), source='*', required=False, allow_blank=True) email_verified = fields.SerializerMethodField(display_table=fields.DisplayMode.SUPPRESS) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def suppress_action(self, action, request, viewset): if request and request.user and not request.user.is_staff and action.name in ('add', 'edit', 'delete'): return True return super().suppress_action(action, request, viewset) def get_email(self, obj): email = None if hasattr(obj, 'emailaddress_set'): for e in obj.emailaddress_set.all(): if e.verified and (not email or e.email == obj.email): # email mora biti verificiran, da povozi onega iz userja, # če pa je še enak za povrh, je pa sploh super email = e if email: return email.email, email.verified return None, False def get_full_name(self, obj): if not obj.id: return '' return obj.get_full_name() def validate(self, attrs): res = super().validate(attrs) email = attrs.get('email', None) if not email: return res from allauth.account.models import EmailAddress from rest_framework.exceptions import ValidationError qry = EmailAddress.objects.filter(email=email) if self.instance: qry = qry.exclude(user=self.instance) if qry.exists(): raise ValidationError(dict(email=_('This e-mail address is already associated with another account.'))) return res @transaction.atomic def create(self, validated_data): res = super().create(validated_data) self.update_user_settings(res, validated_data) return res @transaction.atomic def update(self, instance, validated_data): # noinspection PyTypeChecker self.update_user_settings(instance, validated_data) return super().update(instance, validated_data) @staticmethod def update_user_settings(instance, validated_data, *args, **kwargs): from allauth.account.models import EmailAddress email = validated_data.get('email', None) if email and not EmailAddress.objects.filter(user=instance, email=email).exists(): EmailAddress.objects.filter(user=instance).update(primary=False) EmailAddress.objects.create(user=instance, email=email, primary=True, verified=False) def get_email_verified(self, rec): return self.get_email(rec)[1] class Meta: model = get_user_model() fields = ('id', 'full_name', 'username', 'password', 'first_name', 'last_name', 'is_staff', 'is_superuser', 'is_active', 'email', 'email_verified') changed_flds = { 'id': dict(display=DisplayMode.HIDDEN), } for f in ['first_name', 'last_name', 'is_superuser', 'is_active']: # type: fields.RenderMixin changed_flds[f] = dict(display_table=DisplayMode.SUPPRESS)
class ValidatedSerializer(serializers.ModelSerializer): form_titles = { 'table': 'Validated list', 'new': 'New validated object', 'edit': 'Editing validated object', } actions = Actions( TableAction( TablePosition.HEADER, label=_('+ Add (refresh record)'), title=_('Add new record'), action_js= "dynamicforms.newRow('{% url url_reverse|add:'-detail' pk='new' format='html' %}'" ", 'record', __TABLEID__);"), TableAction( TablePosition.HEADER, label=_('+ Add (refresh table)'), title=_('Add new record'), action_js= "dynamicforms.newRow('{% url url_reverse|add:'-detail' pk='new' format='html' %}'" ", 'table', __TABLEID__);"), TableAction( TablePosition.HEADER, label=_('+ Add (no refresh)'), title=_('Add new record'), action_js= "dynamicforms.newRow('{% url url_reverse|add:'-detail' pk='new' format='html' %}'" ", 'no refresh', __TABLEID__);"), TableAction( TablePosition.ROW_CLICK, label=_('Edit'), title=_('Edit record'), action_js= "dynamicforms.editRow('{% url url_reverse|add:'-detail' pk='__ROWID__' format='html'" " %}'.replace('__ROWID__', $(event.target.parentElement).closest('tr[class=\"df-table-row\"]').attr('data-id'))" ", 'record', __TABLEID__);"), TableAction( TablePosition.ROW_END, label=_('Delete (refresh record)'), title=_('Delete record'), action_js= "dynamicforms.deleteRow('{% url url_reverse|add:'-detail' pk=row.id %}', " + "{{row.id}}, 'record', __TABLEID__);"), TableAction( TablePosition.ROW_END, label=_('Delete (refresh table)'), title=_('Delete record'), action_js= "dynamicforms.deleteRow('{% url url_reverse|add:'-detail' pk=row.id %}', " + "{{row.id}}, 'table', __TABLEID__);"), TableAction( TablePosition.ROW_END, label=_('Delete (no refresh)'), title=_('Delete record'), action_js= "dynamicforms.deleteRow('{% url url_reverse|add:'-detail' pk=row.id %}', " + "{{row.id}}, 'no refresh', __TABLEID__);"), # The following action is duplicated unnecessarily just to later eliminate it in suppress_action TableAction( TablePosition.ROW_END, name='del 1', label=_('Delete (no refresh)'), title=_('Delete record'), action_js= "dynamicforms.deleteRow('{% url url_reverse|add:'-detail' pk=row.id %}', " + "{{row.id}}, 'no refresh', __TABLEID__);")) def validate(self, attrs): attrs = super().validate(attrs) if attrs['amount'] != 5: if attrs['code'] != '123': raise ValidationError({ 'amount': 'amount can only be different than 5 if code is "123"' }) if attrs['enabled'] is True and attrs['item_type'] == 3: raise ValidationError( 'When enabled you can only choose from first three item types') return attrs def suppress_action(self, action, request, viewset): if action.name == 'del 1': return True return super().suppress_action(action, request, viewset) class Meta: model = Validated exclude = ()
class RefreshTypesSerializer(serializers.ModelSerializer): form_titles = { 'table': 'Refresh type list', 'new': 'New refresh type object', 'edit': 'Editing refresh type object', } actions = Actions( # Add actions # refresh record TableAction( TablePosition.HEADER, label=_('+ Add (refresh record)'), title=_('Add new record'), action_js= "dynamicforms.newRow('{% url url_reverse|add:'-detail' pk='new' format='html' %}'" ", 'record', __TABLEID__);"), # refresh table TableAction( TablePosition.HEADER, label=_('+ Add (refresh table)'), title=_('Add new record'), action_js= "dynamicforms.newRow('{% url url_reverse|add:'-detail' pk='new' format='html' %}'" ", 'table', __TABLEID__);"), # no refresh TableAction( TablePosition.HEADER, label=_('+ Add (no refresh)'), title=_('Add new record'), action_js= "dynamicforms.newRow('{% url url_reverse|add:'-detail' pk='new' format='html' %}'" ", 'no refresh', __TABLEID__);"), # page reload TableAction( TablePosition.HEADER, label=_('+ Add (page reload)'), title=_('Add new record'), action_js= "dynamicforms.newRow('{% url url_reverse|add:'-detail' pk='new' format='html' %}'" ", 'page', __TABLEID__);"), # redirect TableAction( TablePosition.HEADER, label=_('+ Add (redirect)'), title=_('Add new record'), action_js= "dynamicforms.newRow('{% url url_reverse|add:'-detail' pk='new' format='html' %}'" ", 'redirect:{% url 'validated-list' format='html' %}', __TABLEID__);" ), # custom function TableAction( TablePosition.HEADER, label=_('+ Add (custom function)'), title=_('Add new record'), action_js= "dynamicforms.newRow('{% url url_reverse|add:'-detail' pk='new' format='html' %}'" ", 'testRefreshType', __TABLEID__);"), # Edit actions TableAction( TablePosition.ROW_CLICK, label=_('Edit'), title=_('Edit record'), action_js= "dynamicforms.editRow('{% url url_reverse|add:'-detail' pk='__ROWID__' format='html'" " %}'.replace('__ROWID__', $(event.target.parentElement).closest('tr[class=\"df-table-row\"]').attr('data-id'))" ", 'record', __TABLEID__);"), # Delete actions # refresh record TableAction( TablePosition.ROW_END, label=_('Delete (refresh record)'), title=_('Delete record'), action_js= "dynamicforms.deleteRow('{% url url_reverse|add:'-detail' pk=row.id %}', " + "{{row.id}}, 'record', __TABLEID__);"), # refresh table TableAction( TablePosition.ROW_END, label=_('Delete (refresh table)'), title=_('Delete record'), action_js= "dynamicforms.deleteRow('{% url url_reverse|add:'-detail' pk=row.id %}', " + "{{row.id}}, 'table', __TABLEID__);"), # no refresh TableAction( TablePosition.ROW_END, label=_('Delete (no refresh)'), title=_('Delete record'), action_js= "dynamicforms.deleteRow('{% url url_reverse|add:'-detail' pk=row.id %}', " + "{{row.id}}, 'no refresh', __TABLEID__);"), # The following action is duplicated unnecessarily just to later eliminate it in suppress_action TableAction( TablePosition.ROW_END, name='del 1', label=_('Delete (no refresh)'), title=_('Delete record'), action_js= "dynamicforms.deleteRow('{% url url_reverse|add:'-detail' pk=row.id %}', " + "{{row.id}}, 'no refresh', __TABLEID__);"), # page reload TableAction( TablePosition.ROW_END, label=_('Delete (page reload)'), title=_('Delete record'), action_js= "dynamicforms.deleteRow('{% url url_reverse|add:'-detail' pk=row.id %}', " + "{{row.id}}, 'page', __TABLEID__);"), # redirect TableAction( TablePosition.ROW_END, label=_('Delete (redirect)'), title=_('Delete record'), action_js= "dynamicforms.deleteRow('{% url url_reverse|add:'-detail' pk=row.id %}', " + "{{row.id}}, 'redirect:{% url 'validated-list' format='html' %}', __TABLEID__);" ), # custom function TableAction( TablePosition.ROW_END, label=_('Delete (custom function)'), title=_('Delete record'), action_js= "dynamicforms.deleteRow('{% url url_reverse|add:'-detail' pk=row.id %}', " + "{{row.id}}, 'testRefreshType', __TABLEID__);"), ) rich_text_field = fields.RTFField(required=False, allow_blank=True) def suppress_action(self, action, request, viewset): if action.name == 'del 1': return True return super().suppress_action(action, request, viewset) class Meta: model = RefreshType exclude = ()