class SimpleArrayField(forms.CharField): default_error_messages = { 'item_invalid': _('Item %(nth)s in the array did not validate:'), } def __init__(self, base_field, *, delimiter=',', max_length=None, min_length=None, **kwargs): self.base_field = base_field self.delimiter = delimiter super().__init__(**kwargs) if min_length is not None: self.min_length = min_length self.validators.append(ArrayMinLengthValidator(int(min_length))) if max_length is not None: self.max_length = max_length self.validators.append(ArrayMaxLengthValidator(int(max_length))) def clean(self, value): value = super().clean(value) return [self.base_field.clean(val) for val in value] def prepare_value(self, value): if isinstance(value, list): return self.delimiter.join( str(self.base_field.prepare_value(v)) for v in value) return value def to_python(self, value): if isinstance(value, list): items = value elif value: items = value.split(self.delimiter) else: items = [] errors = [] values = [] for index, item in enumerate(items): try: values.append(self.base_field.to_python(item)) except ValidationError as error: errors.append( prefix_validation_error( error, prefix=self.error_messages['item_invalid'], code='item_invalid', params={'nth': index + 1}, )) if errors: raise ValidationError(errors) return values def validate(self, value): super().validate(value) errors = [] for index, item in enumerate(value): try: self.base_field.validate(item) except ValidationError as error: errors.append( prefix_validation_error( error, prefix=self.error_messages['item_invalid'], code='item_invalid', params={'nth': index + 1}, )) if errors: raise ValidationError(errors) def run_validators(self, value): super().run_validators(value) errors = [] for index, item in enumerate(value): try: self.base_field.run_validators(item) except ValidationError as error: errors.append( prefix_validation_error( error, prefix=self.error_messages['item_invalid'], code='item_invalid', params={'nth': index + 1}, )) if errors: raise ValidationError(errors) def has_changed(self, initial, data): try: value = self.to_python(data) except ValidationError: pass else: if initial in self.empty_values and value in self.empty_values: return False return super().has_changed(initial, data)
class LineStringField(GeometryField): geom_type = 'LINESTRING' geom_class = LineString form_class = forms.LineStringField description = _("Line string")
class MultiPointField(GeometryField): geom_type = 'MULTIPOINT' geom_class = MultiPoint form_class = forms.MultiPointField description = _("Multi-point")
class ImageField(FileField): attr_class = ImageFieldFile descriptor_class = ImageFileDescriptor description = _("Image") def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs): self.width_field, self.height_field = width_field, height_field super().__init__(verbose_name, name, **kwargs) def check(self, **kwargs): return [ *super().check(**kwargs), *self._check_image_library_installed(), ] def _check_image_library_installed(self): try: from PIL import Image # NOQA except ImportError: return [ checks.Error( 'Cannot use ImageField because Pillow is not installed.', hint=('Get Pillow at https://pypi.org/project/Pillow/ ' 'or run command "pip install Pillow".'), obj=self, id='fields.E210', ) ] else: return [] def deconstruct(self): name, path, args, kwargs = super().deconstruct() if self.width_field: kwargs['width_field'] = self.width_field if self.height_field: kwargs['height_field'] = self.height_field return name, path, args, kwargs def contribute_to_class(self, cls, name, **kwargs): super().contribute_to_class(cls, name, **kwargs) # Attach update_dimension_fields so that dimension fields declared # after their corresponding image field don't stay cleared by # Model.__init__, see bug #11196. # Only run post-initialization dimension update on non-abstract models if not cls._meta.abstract: signals.post_init.connect(self.update_dimension_fields, sender=cls) def update_dimension_fields(self, instance, force=False, *args, **kwargs): """ Update field's width and height fields, if defined. This method is hooked up to model's post_init signal to update dimensions after instantiating a model instance. However, dimensions won't be updated if the dimensions fields are already populated. This avoids unnecessary recalculation when loading an object from the database. Dimensions can be forced to update with force=True, which is how ImageFileDescriptor.__set__ calls this method. """ # Nothing to update if the field doesn't have dimension fields or if # the field is deferred. has_dimension_fields = self.width_field or self.height_field if not has_dimension_fields or self.attname not in instance.__dict__: return # getattr will call the ImageFileDescriptor's __get__ method, which # coerces the assigned value into an instance of self.attr_class # (ImageFieldFile in this case). file = getattr(instance, self.attname) # Nothing to update if we have no file and not being forced to update. if not file and not force: return dimension_fields_filled = not( (self.width_field and not getattr(instance, self.width_field)) or (self.height_field and not getattr(instance, self.height_field)) ) # When both dimension fields have values, we are most likely loading # data from the database or updating an image field that already had # an image stored. In the first case, we don't want to update the # dimension fields because we are already getting their values from the # database. In the second case, we do want to update the dimensions # fields and will skip this return because force will be True since we # were called from ImageFileDescriptor.__set__. if dimension_fields_filled and not force: return # file should be an instance of ImageFieldFile or should be None. if file: width = file.width height = file.height else: # No file, so clear dimensions fields. width = None height = None # Update the width and height fields. if self.width_field: setattr(instance, self.width_field, width) if self.height_field: setattr(instance, self.height_field, height) def formfield(self, **kwargs): return super().formfield(**{ 'form_class': forms.ImageField, **kwargs, })
class GeometryField(BaseSpatialField): """ The base Geometry field -- maps to the OpenGIS Specification Geometry type. """ description = _("The base Geometry field -- maps to the OpenGIS Specification Geometry type.") form_class = forms.GeometryField # The OpenGIS Geometry name. geom_type = 'GEOMETRY' geom_class = None def __init__(self, verbose_name=None, dim=2, geography=False, *, extent=(-180.0, -90.0, 180.0, 90.0), tolerance=0.05, **kwargs): """ The initialization function for geometry fields. In addition to the parameters from BaseSpatialField, it takes the following as keyword arguments: dim: The number of dimensions for this geometry. Defaults to 2. extent: Customize the extent, in a 4-tuple of WGS 84 coordinates, for the geometry field entry in the `USER_SDO_GEOM_METADATA` table. Defaults to (-180.0, -90.0, 180.0, 90.0). tolerance: Define the tolerance, in meters, to use for the geometry field entry in the `USER_SDO_GEOM_METADATA` table. Defaults to 0.05. """ # Setting the dimension of the geometry field. self.dim = dim # Is this a geography rather than a geometry column? self.geography = geography # Oracle-specific private attributes for creating the entry in # `USER_SDO_GEOM_METADATA` self._extent = extent self._tolerance = tolerance super().__init__(verbose_name=verbose_name, **kwargs) def deconstruct(self): name, path, args, kwargs = super().deconstruct() # Include kwargs if they're not the default values. if self.dim != 2: kwargs['dim'] = self.dim if self.geography is not False: kwargs['geography'] = self.geography return name, path, args, kwargs def contribute_to_class(self, cls, name, **kwargs): super().contribute_to_class(cls, name, **kwargs) # Setup for lazy-instantiated Geometry object. setattr(cls, self.attname, SpatialProxy(self.geom_class or GEOSGeometry, self, load_func=GEOSGeometry)) def formfield(self, **kwargs): defaults = { 'form_class': self.form_class, 'geom_type': self.geom_type, 'srid': self.srid, **kwargs, } if self.dim > 2 and not getattr(defaults['form_class'].widget, 'supports_3d', False): defaults.setdefault('widget', forms.Textarea) return super().formfield(**defaults) def select_format(self, compiler, sql, params): """ Return the selection format string, depending on the requirements of the spatial backend. For example, Oracle and MySQL require custom selection formats in order to retrieve geometries in OGC WKB. """ return compiler.connection.ops.select % sql, params
class FloatRangeField(BaseRangeField): default_error_messages = {'invalid': _('Enter two numbers.')} base_field = forms.FloatField range_type = NumericRange
class Meta: verbose_name = _('redirect') verbose_name_plural = _('redirects') db_table = 'server_redirect' unique_together = (('site', 'old_path'), ) ordering = ('old_path', )
class AdminDocsConfig(AppConfig): name = 'server.contrib.admindocs' verbose_name = _("Administrative Documentation")
class MessagesConfig(AppConfig): name = 'server.contrib.messages' verbose_name = _("Messages")
class UserAdmin(admin.ModelAdmin): add_form_template = 'admin/auth/user/add_form.html' change_user_password_template = None fieldsets = ( (None, { 'fields': ('username', 'password') }), (_('Personal info'), { 'fields': ('first_name', 'last_name', 'email') }), (_('Permissions'), { 'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions') }), (_('Important dates'), { 'fields': ('last_login', 'date_joined') }), ) add_fieldsets = ((None, { 'classes': ('wide', ), 'fields': ('username', 'password1', 'password2'), }), ) form = UserChangeForm add_form = UserCreationForm change_password_form = AdminPasswordChangeForm list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff') list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups') search_fields = ('username', 'first_name', 'last_name', 'email') ordering = ('username', ) filter_horizontal = ( 'groups', 'user_permissions', ) def get_fieldsets(self, request, obj=None): if not obj: return self.add_fieldsets return super().get_fieldsets(request, obj) def get_form(self, request, obj=None, **kwargs): """ Use special form during user creation """ defaults = {} if obj is None: defaults['form'] = self.add_form defaults.update(kwargs) return super().get_form(request, obj, **defaults) def get_urls(self): return [ path( '<id>/password/', self.admin_site.admin_view(self.user_change_password), name='auth_user_password_change', ), ] + super().get_urls() def lookup_allowed(self, lookup, value): # Don't allow lookups involving passwords. return not lookup.startswith('password') and super().lookup_allowed( lookup, value) @sensitive_post_parameters_m @csrf_protect_m def add_view(self, request, form_url='', extra_context=None): with transaction.atomic(using=router.db_for_write(self.model)): return self._add_view(request, form_url, extra_context) def _add_view(self, request, form_url='', extra_context=None): # It's an error for a user to have add permission but NOT change # permission for users. If we allowed such users to add users, they # could create superusers, which would mean they would essentially have # the permission to change users. To avoid the problem entirely, we # disallow users from adding users if they don't have change # permission. if not self.has_change_permission(request): if self.has_add_permission(request) and settings.DEBUG: # Raise Http404 in debug mode so that the user gets a helpful # error message. raise Http404( 'Your user does not have the "Change user" permission. In ' 'order to add users, Server requires that your user ' 'account have both the "Add user" and "Change user" ' 'permissions set.') raise PermissionDenied if extra_context is None: extra_context = {} username_field = self.model._meta.get_field(self.model.USERNAME_FIELD) defaults = { 'auto_populated_fields': (), 'username_help_text': username_field.help_text, } extra_context.update(defaults) return super().add_view(request, form_url, extra_context) @sensitive_post_parameters_m def user_change_password(self, request, id, form_url=''): if not self.has_change_permission(request): raise PermissionDenied user = self.get_object(request, unquote(id)) if user is None: raise Http404( _('%(name)s object with primary key %(key)r does not exist.') % { 'name': self.model._meta.verbose_name, 'key': escape(id), }) if request.method == 'POST': form = self.change_password_form(user, request.POST) if form.is_valid(): form.save() change_message = self.construct_change_message( request, form, None) self.log_change(request, user, change_message) msg = gettext('Password changed successfully.') messages.success(request, msg) update_session_auth_hash(request, form.user) return HttpResponseRedirect( reverse( '%s:%s_%s_change' % ( self.admin_site.name, user._meta.app_label, user._meta.model_name, ), args=(user.pk, ), )) else: form = self.change_password_form(user) fieldsets = [(None, {'fields': list(form.base_fields)})] adminForm = admin.helpers.AdminForm(form, fieldsets, {}) context = { 'title': _('Change password: %s') % escape(user.get_username()), 'adminForm': adminForm, 'form_url': form_url, 'form': form, 'is_popup': (IS_POPUP_VAR in request.POST or IS_POPUP_VAR in request.GET), 'add': True, 'change': False, 'has_delete_permission': False, 'has_change_permission': True, 'has_absolute_url': False, 'opts': self.model._meta, 'original': user, 'save_as': False, 'show_save': True, **self.admin_site.each_context(request), } request.current_app = self.admin_site.name return TemplateResponse( request, self.change_user_password_template or 'admin/auth/user/change_password.html', context, ) def response_add(self, request, obj, post_url_continue=None): """ Determine the HttpResponse for the add_view stage. It mostly defers to its superclass implementation but is customized because the User model has a slightly different workflow. """ # We should allow further modification of the user just added i.e. the # 'Save' button should behave like the 'Save and continue editing' # button except in two scenarios: # * The user has pressed the 'Save and add another' button # * We are adding a user in a popup if '_addanother' not in request.POST and IS_POPUP_VAR not in request.POST: request.POST = request.POST.copy() request.POST['_continue'] = 1 return super().response_add(request, obj, post_url_continue)
class SiteMapsConfig(AppConfig): name = 'server.contrib.sitemaps' verbose_name = _("Site Maps")
def user_change_password(self, request, id, form_url=''): if not self.has_change_permission(request): raise PermissionDenied user = self.get_object(request, unquote(id)) if user is None: raise Http404( _('%(name)s object with primary key %(key)r does not exist.') % { 'name': self.model._meta.verbose_name, 'key': escape(id), }) if request.method == 'POST': form = self.change_password_form(user, request.POST) if form.is_valid(): form.save() change_message = self.construct_change_message( request, form, None) self.log_change(request, user, change_message) msg = gettext('Password changed successfully.') messages.success(request, msg) update_session_auth_hash(request, form.user) return HttpResponseRedirect( reverse( '%s:%s_%s_change' % ( self.admin_site.name, user._meta.app_label, user._meta.model_name, ), args=(user.pk, ), )) else: form = self.change_password_form(user) fieldsets = [(None, {'fields': list(form.base_fields)})] adminForm = admin.helpers.AdminForm(form, fieldsets, {}) context = { 'title': _('Change password: %s') % escape(user.get_username()), 'adminForm': adminForm, 'form_url': form_url, 'form': form, 'is_popup': (IS_POPUP_VAR in request.POST or IS_POPUP_VAR in request.GET), 'add': True, 'change': False, 'has_delete_permission': False, 'has_change_permission': True, 'has_absolute_url': False, 'opts': self.model._meta, 'original': user, 'save_as': False, 'show_save': True, **self.admin_site.each_context(request), } request.current_app = self.admin_site.name return TemplateResponse( request, self.change_user_password_template or 'admin/auth/user/change_password.html', context, )
class AbstractBaseUser(models.Model): password = models.CharField(_('password'), max_length=128) last_login = models.DateTimeField(_('last login'), blank=True, null=True) is_active = True REQUIRED_FIELDS = [] # Stores the raw password if set_password() is called so that it can # be passed to password_changed() after the model is saved. _password = None class Meta: abstract = True def get_username(self): "Return the identifying username for this User" return getattr(self, self.USERNAME_FIELD) def __str__(self): return self.get_username() def clean(self): setattr(self, self.USERNAME_FIELD, self.normalize_username(self.get_username())) def save(self, *args, **kwargs): super().save(*args, **kwargs) if self._password is not None: password_validation.password_changed(self._password, self) self._password = None def natural_key(self): return (self.get_username(), ) @property def is_anonymous(self): """ Always return False. This is a way of comparing User objects to anonymous users. """ return False @property def is_authenticated(self): """ Always return True. This is a way to tell if the user has been authenticated in templates. """ return True def set_password(self, raw_password): self.password = make_password(raw_password) self._password = raw_password def check_password(self, raw_password): """ Return a boolean of whether the raw_password was correct. Handles hashing formats behind the scenes. """ def setter(raw_password): self.set_password(raw_password) # Password hash upgrades shouldn't be considered password changes. self._password = None self.save(update_fields=["password"]) return check_password(raw_password, self.password, setter) def set_unusable_password(self): # Set a value that will never be a valid hash self.password = make_password(None) def has_usable_password(self): """ Return False if set_unusable_password() has been called for this user. """ return is_password_usable(self.password) def get_session_auth_hash(self): """ Return an HMAC of the password field. """ key_salt = "server.contrib.auth.models.AbstractBaseUser.get_session_auth_hash" return salted_hmac(key_salt, self.password).hexdigest() @classmethod def get_email_field_name(cls): try: return cls.EMAIL_FIELD except AttributeError: return 'email' @classmethod def normalize_username(cls, username): return unicodedata.normalize('NFKC', username) if isinstance( username, str) else username
def safe_summary(self, encoded): return OrderedDict([ (_('algorithm'), self.algorithm), (_('hash'), mask_hash(encoded, show=3)), ])
class FlatPagesConfig(AppConfig): name = 'server.contrib.flatpages' verbose_name = _("Flat Pages")
class ClearableFileInput(FileInput): clear_checkbox_label = _('Clear') initial_text = _('Currently') input_text = _('Change') template_name = 'server/forms/widgets/clearable_file_input.html' def clear_checkbox_name(self, name): """ Given the name of the file input, return the name of the clear checkbox input. """ return name + '-clear' def clear_checkbox_id(self, name): """ Given the name of the clear checkbox input, return the HTML id for it. """ return name + '_id' def is_initial(self, value): """ Return whether value is considered to be initial value. """ return bool(value and getattr(value, 'url', False)) def format_value(self, value): """ Return the file object if it has a defined url attribute. """ if self.is_initial(value): return value def get_context(self, name, value, attrs): context = super().get_context(name, value, attrs) checkbox_name = self.clear_checkbox_name(name) checkbox_id = self.clear_checkbox_id(checkbox_name) context['widget'].update({ 'checkbox_name': checkbox_name, 'checkbox_id': checkbox_id, 'is_initial': self.is_initial(value), 'input_text': self.input_text, 'initial_text': self.initial_text, 'clear_checkbox_label': self.clear_checkbox_label, }) return context def value_from_datadict(self, data, files, name): upload = super().value_from_datadict(data, files, name) if not self.is_required and CheckboxInput().value_from_datadict( data, files, self.clear_checkbox_name(name)): if upload: # If the user contradicts themselves (uploads a new file AND # checks the "clear" checkbox), we return a unique marker # object that FileField will turn into a ValidationError. return FILE_INPUT_CONTRADICTION # False signals to clear any existing value, as opposed to just None return False return upload def use_required_attribute(self, initial): return super().use_required_attribute(initial) and not initial def value_omitted_from_data(self, data, files, name): return ( super().value_omitted_from_data(data, files, name) and self.clear_checkbox_name(name) not in data )
class IntegerRangeField(BaseRangeField): default_error_messages = {'invalid': _('Enter two whole numbers.')} base_field = forms.IntegerField range_type = NumericRange
class Meta: db_table = 'server_flatpage' verbose_name = _('flat page') verbose_name_plural = _('flat pages') ordering = ('url', )
class DateRangeField(BaseRangeField): default_error_messages = {'invalid': _('Enter two valid dates.')} base_field = forms.DateField range_type = DateRange
def delete_selected(modeladmin, request, queryset): """ Default action which deletes the selected objects. This action first displays a confirmation page which shows all the deletable objects, or, if the user has no permission one of the related childs (foreignkeys), a "permission denied" message. Next, it deletes all selected objects and redirects back to the change list. """ opts = modeladmin.model._meta app_label = opts.app_label # Populate deletable_objects, a data structure of all related objects that # will also be deleted. deletable_objects, model_count, perms_needed, protected = modeladmin.get_deleted_objects(queryset, request) # The user has already confirmed the deletion. # Do the deletion and return None to display the change list view again. if request.POST.get('post') and not protected: if perms_needed: raise PermissionDenied n = queryset.count() if n: for obj in queryset: obj_display = str(obj) modeladmin.log_deletion(request, obj, obj_display) modeladmin.delete_queryset(request, queryset) modeladmin.message_user(request, _("Successfully deleted %(count)d %(items)s.") % { "count": n, "items": model_ngettext(modeladmin.opts, n) }, messages.SUCCESS) # Return None to display the change list page again. return None objects_name = model_ngettext(queryset) if perms_needed or protected: title = _("Cannot delete %(name)s") % {"name": objects_name} else: title = _("Are you sure?") context = { **modeladmin.admin_site.each_context(request), 'title': title, 'objects_name': str(objects_name), 'deletable_objects': [deletable_objects], 'model_count': dict(model_count).items(), 'queryset': queryset, 'perms_lacking': perms_needed, 'protected': protected, 'opts': opts, 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME, 'media': modeladmin.media, } request.current_app = modeladmin.admin_site.name # Display the confirmation page return TemplateResponse(request, modeladmin.delete_selected_confirmation_template or [ "admin/%s/%s/delete_selected_confirmation.html" % (app_label, opts.model_name), "admin/%s/delete_selected_confirmation.html" % app_label, "admin/delete_selected_confirmation.html" ], context)
class FileField(Field): # The class to wrap instance attributes in. Accessing the file object off # the instance will always return an instance of attr_class. attr_class = FieldFile # The descriptor to use for accessing the attribute off of the class. descriptor_class = FileDescriptor description = _("File") def __init__(self, verbose_name=None, name=None, upload_to='', storage=None, **kwargs): self._primary_key_set_explicitly = 'primary_key' in kwargs self.storage = storage or default_storage self.upload_to = upload_to kwargs.setdefault('max_length', 100) super().__init__(verbose_name, name, **kwargs) def check(self, **kwargs): return [ *super().check(**kwargs), *self._check_primary_key(), *self._check_upload_to(), ] def _check_primary_key(self): if self._primary_key_set_explicitly: return [ checks.Error( "'primary_key' is not a valid argument for a %s." % self.__class__.__name__, obj=self, id='fields.E201', ) ] else: return [] def _check_upload_to(self): if isinstance(self.upload_to, str) and self.upload_to.startswith('/'): return [ checks.Error( "%s's 'upload_to' argument must be a relative path, not an " "absolute path." % self.__class__.__name__, obj=self, id='fields.E202', hint='Remove the leading slash.', ) ] else: return [] def deconstruct(self): name, path, args, kwargs = super().deconstruct() if kwargs.get("max_length") == 100: del kwargs["max_length"] kwargs['upload_to'] = self.upload_to if self.storage is not default_storage: kwargs['storage'] = self.storage return name, path, args, kwargs def get_internal_type(self): return "FileField" def get_prep_value(self, value): value = super().get_prep_value(value) # Need to convert File objects provided via a form to string for database insertion if value is None: return None return str(value) def pre_save(self, model_instance, add): file = super().pre_save(model_instance, add) if file and not file._committed: # Commit the file to storage prior to saving the model file.save(file.name, file.file, save=False) return file def contribute_to_class(self, cls, name, **kwargs): super().contribute_to_class(cls, name, **kwargs) setattr(cls, self.name, self.descriptor_class(self)) def generate_filename(self, instance, filename): """ Apply (if callable) or prepend (if a string) upload_to to the filename, then delegate further processing of the name to the storage backend. Until the storage layer, all file paths are expected to be Unix style (with forward slashes). """ if callable(self.upload_to): filename = self.upload_to(instance, filename) else: dirname = datetime.datetime.now().strftime(self.upload_to) filename = posixpath.join(dirname, filename) return self.storage.generate_filename(filename) def save_form_data(self, instance, data): # Important: None means "no change", other false value means "clear" # This subtle distinction (rather than a more explicit marker) is # needed because we need to consume values that are also sane for a # regular (non Model-) Form to find in its cleaned_data dictionary. if data is not None: # This value will be converted to str and stored in the # database, so leaving False as-is is not acceptable. setattr(instance, self.name, data or '') def formfield(self, **kwargs): return super().formfield(**{ 'form_class': forms.FileField, 'max_length': self.max_length, **kwargs, })
class MultiValueField(Field): """ Aggregate the logic of multiple Fields. Its clean() method takes a "decompressed" list of values, which are then cleaned into a single value according to self.fields. Each value in this list is cleaned by the corresponding field -- the first value is cleaned by the first field, the second value is cleaned by the second field, etc. Once all fields are cleaned, the list of clean values is "compressed" into a single value. Subclasses should not have to implement clean(). Instead, they must implement compress(), which takes a list of valid values and returns a "compressed" version of those values -- a single value. You'll probably want to use this with MultiWidget. """ default_error_messages = { 'invalid': _('Enter a list of values.'), 'incomplete': _('Enter a complete value.'), } def __init__(self, fields, *, require_all_fields=True, **kwargs): self.require_all_fields = require_all_fields super().__init__(**kwargs) for f in fields: f.error_messages.setdefault('incomplete', self.error_messages['incomplete']) if self.disabled: f.disabled = True if self.require_all_fields: # Set 'required' to False on the individual fields, because the # required validation will be handled by MultiValueField, not # by those individual fields. f.required = False self.fields = fields def __deepcopy__(self, memo): result = super().__deepcopy__(memo) result.fields = tuple(x.__deepcopy__(memo) for x in self.fields) return result def validate(self, value): pass def clean(self, value): """ Validate every value in the given list. A value is validated against the corresponding Field in self.fields. For example, if this MultiValueField was instantiated with fields=(DateField(), TimeField()), clean() would call DateField.clean(value[0]) and TimeField.clean(value[1]). """ clean_data = [] errors = [] if self.disabled and not isinstance(value, list): value = self.widget.decompress(value) if not value or isinstance(value, (list, tuple)): if not value or not [ v for v in value if v not in self.empty_values ]: if self.required: raise ValidationError(self.error_messages['required'], code='required') else: return self.compress([]) else: raise ValidationError(self.error_messages['invalid'], code='invalid') for i, field in enumerate(self.fields): try: field_value = value[i] except IndexError: field_value = None if field_value in self.empty_values: if self.require_all_fields: # Raise a 'required' error if the MultiValueField is # required and any field is empty. if self.required: raise ValidationError(self.error_messages['required'], code='required') elif field.required: # Otherwise, add an 'incomplete' error to the list of # collected errors and skip field cleaning, if a required # field is empty. if field.error_messages['incomplete'] not in errors: errors.append(field.error_messages['incomplete']) continue try: clean_data.append(field.clean(field_value)) except ValidationError as e: # Collect all validation errors in a single list, which we'll # raise at the end of clean(), rather than raising a single # exception for the first error we encounter. Skip duplicates. errors.extend(m for m in e.error_list if m not in errors) if errors: raise ValidationError(errors) out = self.compress(clean_data) self.validate(out) self.run_validators(out) return out def compress(self, data_list): """ Return a single value for the given list of values. The values can be assumed to be valid. For example, if this MultiValueField was instantiated with fields=(DateField(), TimeField()), this might return a datetime object created by combining the date and time in data_list. """ raise NotImplementedError('Subclasses must implement this method.') def has_changed(self, initial, data): if self.disabled: return False if initial is None: initial = ['' for x in range(0, len(data))] else: if not isinstance(initial, list): initial = self.widget.decompress(initial) for field, initial, data in zip(self.fields, initial, data): try: initial = field.to_python(initial) except ValidationError: return True if field.has_changed(initial, data): return True return False
class SessionsConfig(AppConfig): name = 'server.contrib.sessions' verbose_name = _("Sessions")
class FileField(Field): widget = ClearableFileInput default_error_messages = { 'invalid': _("No file was submitted. Check the encoding type on the form."), 'missing': _("No file was submitted."), 'empty': _("The submitted file is empty."), 'max_length': ngettext_lazy( 'Ensure this filename has at most %(max)d character (it has %(length)d).', 'Ensure this filename has at most %(max)d characters (it has %(length)d).', 'max'), 'contradiction': _('Please either submit a file or check the clear checkbox, not both.') } def __init__(self, *, max_length=None, allow_empty_file=False, **kwargs): self.max_length = max_length self.allow_empty_file = allow_empty_file super().__init__(**kwargs) def to_python(self, data): if data in self.empty_values: return None # UploadedFile objects should have name and size attributes. try: file_name = data.name file_size = data.size except AttributeError: raise ValidationError(self.error_messages['invalid'], code='invalid') if self.max_length is not None and len(file_name) > self.max_length: params = {'max': self.max_length, 'length': len(file_name)} raise ValidationError(self.error_messages['max_length'], code='max_length', params=params) if not file_name: raise ValidationError(self.error_messages['invalid'], code='invalid') if not self.allow_empty_file and not file_size: raise ValidationError(self.error_messages['empty'], code='empty') return data def clean(self, data, initial=None): # If the widget got contradictory inputs, we raise a validation error if data is FILE_INPUT_CONTRADICTION: raise ValidationError(self.error_messages['contradiction'], code='contradiction') # False means the field value should be cleared; further validation is # not needed. if data is False: if not self.required: return False # If the field is required, clearing is not possible (the widget # shouldn't return False data in that case anyway). False is not # in self.empty_value; if a False value makes it this far # it should be validated from here on out as None (so it will be # caught by the required check). data = None if not data and initial: return initial return super().clean(data) def bound_data(self, data, initial): if data in (None, FILE_INPUT_CONTRADICTION): return initial return data def has_changed(self, initial, data): return not self.disabled and data is not None
class PointField(GeometryField): geom_type = 'POINT' geom_class = Point form_class = forms.PointField description = _("Point")
class Field: widget = TextInput # Default widget to use when rendering this type of Field. hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden". default_validators = [] # Default set of validators # Add an 'invalid' entry to default_error_message if you want a specific # field error message not raised by the field validators. default_error_messages = { 'required': _('This field is required.'), } empty_values = list(validators.EMPTY_VALUES) def __init__(self, *, required=True, widget=None, label=None, initial=None, help_text='', error_messages=None, show_hidden_initial=False, validators=(), localize=False, disabled=False, label_suffix=None): # required -- Boolean that specifies whether the field is required. # True by default. # widget -- A Widget class, or instance of a Widget class, that should # be used for this Field when displaying it. Each Field has a # default Widget that it'll use if you don't specify this. In # most cases, the default widget is TextInput. # label -- A verbose name for this field, for use in displaying this # field in a form. By default, Server will use a "pretty" # version of the form field name, if the Field is part of a # Form. # initial -- A value to use in this Field's initial display. This value # is *not* used as a fallback if data isn't given. # help_text -- An optional string to use as "help text" for this Field. # error_messages -- An optional dictionary to override the default # messages that the field will raise. # show_hidden_initial -- Boolean that specifies if it is needed to render a # hidden widget with initial value after widget. # validators -- List of additional validators to use # localize -- Boolean that specifies if the field should be localized. # disabled -- Boolean that specifies whether the field is disabled, that # is its widget is shown in the form but not editable. # label_suffix -- Suffix to be added to the label. Overrides # form's label_suffix. self.required, self.label, self.initial = required, label, initial self.show_hidden_initial = show_hidden_initial self.help_text = help_text self.disabled = disabled self.label_suffix = label_suffix widget = widget or self.widget if isinstance(widget, type): widget = widget() else: widget = copy.deepcopy(widget) # Trigger the localization machinery if needed. self.localize = localize if self.localize: widget.is_localized = True # Let the widget know whether it should display as required. widget.is_required = self.required # Hook into self.widget_attrs() for any Field-specific HTML attributes. extra_attrs = self.widget_attrs(widget) if extra_attrs: widget.attrs.update(extra_attrs) self.widget = widget messages = {} for c in reversed(self.__class__.__mro__): messages.update(getattr(c, 'default_error_messages', {})) messages.update(error_messages or {}) self.error_messages = messages self.validators = [*self.default_validators, *validators] super().__init__() def prepare_value(self, value): return value def to_python(self, value): return value def validate(self, value): if value in self.empty_values and self.required: raise ValidationError(self.error_messages['required'], code='required') def run_validators(self, value): if value in self.empty_values: return errors = [] for v in self.validators: try: v(value) except ValidationError as e: if hasattr(e, 'code') and e.code in self.error_messages: e.message = self.error_messages[e.code] errors.extend(e.error_list) if errors: raise ValidationError(errors) def clean(self, value): """ Validate the given value and return its "cleaned" value as an appropriate Python object. Raise ValidationError for any errors. """ value = self.to_python(value) self.validate(value) self.run_validators(value) return value def bound_data(self, data, initial): """ Return the value that should be shown for this field on render of a bound form, given the submitted POST data for the field and the initial data, if any. For most fields, this will simply be data; FileFields need to handle it a bit differently. """ if self.disabled: return initial return data def widget_attrs(self, widget): """ Given a Widget instance (*not* a Widget class), return a dictionary of any HTML attributes that should be added to the Widget, based on this Field. """ return {} def has_changed(self, initial, data): """Return True if data differs from initial.""" # Always return False if the field is disabled since self.bound_data # always uses the initial value in this case. if self.disabled: return False try: data = self.to_python(data) if hasattr(self, '_coerce'): return self._coerce(data) != self._coerce(initial) except ValidationError: return True # For purposes of seeing whether something has changed, None is # the same as an empty string, if the data or initial value we get # is None, replace it with ''. initial_value = initial if initial is not None else '' data_value = data if data is not None else '' return initial_value != data_value def get_bound_field(self, form, field_name): """ Return a BoundField instance that will be used when accessing the form field in a template. """ return BoundField(form, self, field_name) def __deepcopy__(self, memo): result = copy.copy(self) memo[id(self)] = result result.widget = copy.deepcopy(self.widget, memo) result.validators = self.validators[:] return result
class PolygonField(GeometryField): geom_type = 'POLYGON' geom_class = Polygon form_class = forms.PolygonField description = _("Polygon")
class ChoiceField(Field): widget = Select default_error_messages = { 'invalid_choice': _('Select a valid choice. %(value)s is not one of the available choices.' ), } def __init__(self, *, choices=(), **kwargs): super().__init__(**kwargs) self.choices = choices def __deepcopy__(self, memo): result = super().__deepcopy__(memo) result._choices = copy.deepcopy(self._choices, memo) return result def _get_choices(self): return self._choices def _set_choices(self, value): # Setting choices also sets the choices on the widget. # choices can be any iterable, but we call list() on it because # it will be consumed more than once. if callable(value): value = CallableChoiceIterator(value) else: value = list(value) self._choices = self.widget.choices = value choices = property(_get_choices, _set_choices) def to_python(self, value): """Return a string.""" if value in self.empty_values: return '' return str(value) def validate(self, value): """Validate that the input is in self.choices.""" super().validate(value) if value and not self.valid_value(value): raise ValidationError( self.error_messages['invalid_choice'], code='invalid_choice', params={'value': value}, ) def valid_value(self, value): """Check to see if the provided value is a valid choice.""" text_value = str(value) for k, v in self.choices: if isinstance(v, (list, tuple)): # This is an optgroup, so look inside the group for options for k2, v2 in v: if value == k2 or text_value == str(k2): return True else: if value == k or text_value == str(k): return True return False
class MultiLineStringField(GeometryField): geom_type = 'MULTILINESTRING' geom_class = MultiLineString form_class = forms.MultiLineStringField description = _("Multi-line string")
class PasswordResetConfirmView(PasswordContextMixin, FormView): form_class = SetPasswordForm post_reset_login = False post_reset_login_backend = None success_url = reverse_lazy('password_reset_complete') template_name = 'registration/password_reset_confirm.html' title = _('Enter new password') token_generator = default_token_generator @method_decorator(sensitive_post_parameters()) @method_decorator(never_cache) def dispatch(self, *args, **kwargs): assert 'uidb64' in kwargs and 'token' in kwargs self.validlink = False self.user = self.get_user(kwargs['uidb64']) if self.user is not None: token = kwargs['token'] if token == INTERNAL_RESET_URL_TOKEN: session_token = self.request.session.get( INTERNAL_RESET_SESSION_TOKEN) if self.token_generator.check_token(self.user, session_token): # If the token is valid, display the password reset form. self.validlink = True return super().dispatch(*args, **kwargs) else: if self.token_generator.check_token(self.user, token): # Store the token in the session and redirect to the # password reset form at a URL without the token. That # avoids the possibility of leaking the token in the # HTTP Referer header. self.request.session[INTERNAL_RESET_SESSION_TOKEN] = token redirect_url = self.request.path.replace( token, INTERNAL_RESET_URL_TOKEN) return HttpResponseRedirect(redirect_url) # Display the "Password reset unsuccessful" page. return self.render_to_response(self.get_context_data()) def get_user(self, uidb64): try: # urlsafe_base64_decode() decodes to bytestring uid = urlsafe_base64_decode(uidb64).decode() user = UserModel._default_manager.get(pk=uid) except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist, ValidationError): user = None return user def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.user return kwargs def form_valid(self, form): user = form.save() del self.request.session[INTERNAL_RESET_SESSION_TOKEN] if self.post_reset_login: auth_login(self.request, user, self.post_reset_login_backend) return super().form_valid(form) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) if self.validlink: context['validlink'] = True else: context.update({ 'form': None, 'title': _('Password reset unsuccessful'), 'validlink': False, }) return context