测试gitnore

This commit is contained in:
ladeng07
2022-05-06 15:45:57 +08:00
parent 12f390949b
commit 51552904f9
2347 changed files with 120102 additions and 53549 deletions
@@ -11,10 +11,10 @@ from django.views.decorators.debug import sensitive_variables
from .signals import user_logged_in, user_logged_out, user_login_failed
SESSION_KEY = "_auth_user_id"
BACKEND_SESSION_KEY = "_auth_user_backend"
HASH_SESSION_KEY = "_auth_user_hash"
REDIRECT_FIELD_NAME = "next"
SESSION_KEY = '_auth_user_id'
BACKEND_SESSION_KEY = '_auth_user_backend'
HASH_SESSION_KEY = '_auth_user_hash'
REDIRECT_FIELD_NAME = 'next'
def load_backend(path):
@@ -28,8 +28,8 @@ def _get_backends(return_tuples=False):
backends.append((backend, backend_path) if return_tuples else backend)
if not backends:
raise ImproperlyConfigured(
"No authentication backends have been defined. Does "
"AUTHENTICATION_BACKENDS contain anything?"
'No authentication backends have been defined. Does '
'AUTHENTICATION_BACKENDS contain anything?'
)
return backends
@@ -38,7 +38,7 @@ def get_backends():
return _get_backends(return_tuples=False)
@sensitive_variables("credentials")
@sensitive_variables('credentials')
def _clean_credentials(credentials):
"""
Clean a dictionary of credentials of potentially sensitive info before
@@ -46,8 +46,8 @@ def _clean_credentials(credentials):
Not comprehensive - intended for user_login_failed signal
"""
SENSITIVE_CREDENTIALS = re.compile("api|token|key|secret|password|signature", re.I)
CLEANSED_SUBSTITUTE = "********************"
SENSITIVE_CREDENTIALS = re.compile('api|token|key|secret|password|signature', re.I)
CLEANSED_SUBSTITUTE = '********************'
for key in credentials:
if SENSITIVE_CREDENTIALS.search(key):
credentials[key] = CLEANSED_SUBSTITUTE
@@ -60,7 +60,7 @@ def _get_user_session_key(request):
return get_user_model()._meta.pk.to_python(request.session[SESSION_KEY])
@sensitive_variables("credentials")
@sensitive_variables('credentials')
def authenticate(request=None, **credentials):
"""
If the given credentials are valid, return a User object.
@@ -70,14 +70,12 @@ def authenticate(request=None, **credentials):
try:
backend_signature.bind(request, **credentials)
except TypeError:
# This backend doesn't accept these credentials as arguments. Try
# the next one.
# This backend doesn't accept these credentials as arguments. Try the next one.
continue
try:
user = backend.authenticate(request, **credentials)
except PermissionDenied:
# This backend says to stop in our tracks - this user should not be
# allowed in at all.
# This backend says to stop in our tracks - this user should not be allowed in at all.
break
if user is None:
continue
@@ -86,9 +84,7 @@ def authenticate(request=None, **credentials):
return user
# The credentials supplied are invalid to all backends, fire signal
user_login_failed.send(
sender=__name__, credentials=_clean_credentials(credentials), request=request
)
user_login_failed.send(sender=__name__, credentials=_clean_credentials(credentials), request=request)
def login(request, user, backend=None):
@@ -97,19 +93,16 @@ def login(request, user, backend=None):
have to reauthenticate on every request. Note that data set during
the anonymous session is retained when the user logs in.
"""
session_auth_hash = ""
session_auth_hash = ''
if user is None:
user = request.user
if hasattr(user, "get_session_auth_hash"):
if hasattr(user, 'get_session_auth_hash'):
session_auth_hash = user.get_session_auth_hash()
if SESSION_KEY in request.session:
if _get_user_session_key(request) != user.pk or (
session_auth_hash
and not constant_time_compare(
request.session.get(HASH_SESSION_KEY, ""), session_auth_hash
)
):
session_auth_hash and
not constant_time_compare(request.session.get(HASH_SESSION_KEY, ''), session_auth_hash)):
# To avoid reusing another user's session, create a new, empty
# session if the existing session corresponds to a different
# authenticated user.
@@ -125,20 +118,18 @@ def login(request, user, backend=None):
_, backend = backends[0]
else:
raise ValueError(
"You have multiple authentication backends configured and "
"therefore must provide the `backend` argument or set the "
"`backend` attribute on the user."
'You have multiple authentication backends configured and '
'therefore must provide the `backend` argument or set the '
'`backend` attribute on the user.'
)
else:
if not isinstance(backend, str):
raise TypeError(
"backend must be a dotted import path string (got %r)." % backend
)
raise TypeError('backend must be a dotted import path string (got %r).' % backend)
request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
request.session[BACKEND_SESSION_KEY] = backend
request.session[HASH_SESSION_KEY] = session_auth_hash
if hasattr(request, "user"):
if hasattr(request, 'user'):
request.user = user
rotate_token(request)
user_logged_in.send(sender=user.__class__, request=request, user=user)
@@ -151,14 +142,13 @@ def logout(request):
"""
# Dispatch the signal before the user is logged out so the receivers have a
# chance to find out *who* logged out.
user = getattr(request, "user", None)
if not getattr(user, "is_authenticated", True):
user = getattr(request, 'user', None)
if not getattr(user, 'is_authenticated', True):
user = None
user_logged_out.send(sender=user.__class__, request=request, user=user)
request.session.flush()
if hasattr(request, "user"):
if hasattr(request, 'user'):
from django.contrib.auth.models import AnonymousUser
request.user = AnonymousUser()
@@ -169,13 +159,10 @@ def get_user_model():
try:
return django_apps.get_model(settings.AUTH_USER_MODEL, require_ready=False)
except ValueError:
raise ImproperlyConfigured(
"AUTH_USER_MODEL must be of the form 'app_label.model_name'"
)
raise ImproperlyConfigured("AUTH_USER_MODEL must be of the form 'app_label.model_name'")
except LookupError:
raise ImproperlyConfigured(
"AUTH_USER_MODEL refers to model '%s' that has not been installed"
% settings.AUTH_USER_MODEL
"AUTH_USER_MODEL refers to model '%s' that has not been installed" % settings.AUTH_USER_MODEL
)
@@ -185,7 +172,6 @@ def get_user(request):
If no user is retrieved, return an instance of `AnonymousUser`.
"""
from .models import AnonymousUser
user = None
try:
user_id = _get_user_session_key(request)
@@ -197,14 +183,20 @@ def get_user(request):
backend = load_backend(backend_path)
user = backend.get_user(user_id)
# Verify the session
if hasattr(user, "get_session_auth_hash"):
if hasattr(user, 'get_session_auth_hash'):
session_hash = request.session.get(HASH_SESSION_KEY)
session_hash_verified = session_hash and constant_time_compare(
session_hash, user.get_session_auth_hash()
session_hash,
user.get_session_auth_hash()
)
if not session_hash_verified:
request.session.flush()
user = None
if not (
session_hash and
hasattr(user, '_legacy_get_session_auth_hash') and
constant_time_compare(session_hash, user._legacy_get_session_auth_hash())
):
request.session.flush()
user = None
return user or AnonymousUser()
@@ -213,7 +205,7 @@ def get_permission_codename(action, opts):
"""
Return the codename of the permission for the specified action.
"""
return "%s_%s" % (action, opts.model_name)
return '%s_%s' % (action, opts.model_name)
def update_session_auth_hash(request, user):
@@ -226,5 +218,5 @@ def update_session_auth_hash(request, user):
password was changed.
"""
request.session.cycle_key()
if hasattr(user, "get_session_auth_hash") and request.user == user:
if hasattr(user, 'get_session_auth_hash') and request.user == user:
request.session[HASH_SESSION_KEY] = user.get_session_auth_hash()
@@ -4,9 +4,7 @@ from django.contrib.admin.options import IS_POPUP_VAR
from django.contrib.admin.utils import unquote
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.forms import (
AdminPasswordChangeForm,
UserChangeForm,
UserCreationForm,
AdminPasswordChangeForm, UserChangeForm, UserCreationForm,
)
from django.contrib.auth.models import Group, User
from django.core.exceptions import PermissionDenied
@@ -16,8 +14,7 @@ from django.template.response import TemplateResponse
from django.urls import path, reverse
from django.utils.decorators import method_decorator
from django.utils.html import escape
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext, gettext_lazy as _
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters
@@ -27,60 +24,45 @@ sensitive_post_parameters_m = method_decorator(sensitive_post_parameters())
@admin.register(Group)
class GroupAdmin(admin.ModelAdmin):
search_fields = ("name",)
ordering = ("name",)
filter_horizontal = ("permissions",)
search_fields = ('name',)
ordering = ('name',)
filter_horizontal = ('permissions',)
def formfield_for_manytomany(self, db_field, request=None, **kwargs):
if db_field.name == "permissions":
qs = kwargs.get("queryset", db_field.remote_field.model.objects)
if db_field.name == 'permissions':
qs = kwargs.get('queryset', db_field.remote_field.model.objects)
# Avoid a major performance hit resolving permission names which
# triggers a content_type load:
kwargs["queryset"] = qs.select_related("content_type")
kwargs['queryset'] = qs.select_related('content_type')
return super().formfield_for_manytomany(db_field, request=request, **kwargs)
@admin.register(User)
class UserAdmin(admin.ModelAdmin):
add_form_template = "admin/auth/user/add_form.html"
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")}),
(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"),
},
),
(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",
)
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:
@@ -93,32 +75,30 @@ class UserAdmin(admin.ModelAdmin):
"""
defaults = {}
if obj is None:
defaults["form"] = self.add_form
defaults['form'] = self.add_form
defaults.update(kwargs)
return super().get_form(request, obj, **defaults)
def get_urls(self):
return [
path(
"<id>/password/",
'<id>/password/',
self.admin_site.admin_view(self.user_change_password),
name="auth_user_password_change",
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
)
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):
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):
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
@@ -131,47 +111,42 @@ class UserAdmin(admin.ModelAdmin):
# error message.
raise Http404(
'Your user does not have the "Change user" permission. In '
"order to add users, Django requires that your user "
'order to add users, Django requires that your user '
'account have both the "Add user" and "Change user" '
"permissions set."
)
'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,
'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=""):
def user_change_password(self, request, id, form_url=''):
user = self.get_object(request, unquote(id))
if not self.has_change_permission(request, user):
raise PermissionDenied
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":
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.")
msg = gettext('Password changed successfully.')
messages.success(request, msg)
update_session_auth_hash(request, form.user)
return HttpResponseRedirect(
reverse(
"%s:%s_%s_change"
% (
'%s:%s_%s_change' % (
self.admin_site.name,
user._meta.app_label,
user._meta.model_name,
@@ -182,25 +157,25 @@ class UserAdmin(admin.ModelAdmin):
else:
form = self.change_password_form(user)
fieldsets = [(None, {"fields": list(form.base_fields)})]
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),
"is_popup_var": IS_POPUP_VAR,
"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,
'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),
}
@@ -208,8 +183,8 @@ class UserAdmin(admin.ModelAdmin):
return TemplateResponse(
request,
self.change_user_password_template
or "admin/auth/user/change_password.html",
self.change_user_password_template or
'admin/auth/user/change_password.html',
context,
)
@@ -224,7 +199,7 @@ class UserAdmin(admin.ModelAdmin):
# 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:
if '_addanother' not in request.POST and IS_POPUP_VAR not in request.POST:
request.POST = request.POST.copy()
request.POST["_continue"] = 1
request.POST['_continue'] = 1
return super().response_add(request, obj, post_url_continue)
@@ -11,20 +11,19 @@ from .signals import user_logged_in
class AuthConfig(AppConfig):
default_auto_field = "django.db.models.AutoField"
name = "django.contrib.auth"
default_auto_field = 'django.db.models.AutoField'
name = 'django.contrib.auth'
verbose_name = _("Authentication and Authorization")
def ready(self):
post_migrate.connect(
create_permissions,
dispatch_uid="django.contrib.auth.management.create_permissions",
dispatch_uid="django.contrib.auth.management.create_permissions"
)
last_login_field = getattr(get_user_model(), "last_login", None)
last_login_field = getattr(get_user_model(), 'last_login', None)
# Register the handler only if UserModel.last_login is a field.
if isinstance(last_login_field, DeferredAttribute):
from .models import update_last_login
user_logged_in.connect(update_last_login, dispatch_uid="update_last_login")
user_logged_in.connect(update_last_login, dispatch_uid='update_last_login')
checks.register(check_user_model, checks.Tags.models)
checks.register(check_models_permissions, checks.Tags.models)
@@ -53,15 +53,15 @@ class ModelBackend(BaseBackend):
Reject users with is_active=False. Custom user models that don't have
that attribute are allowed.
"""
is_active = getattr(user, "is_active", None)
is_active = getattr(user, 'is_active', None)
return is_active or is_active is None
def _get_user_permissions(self, user_obj):
return user_obj.user_permissions.all()
def _get_group_permissions(self, user_obj):
user_groups_field = get_user_model()._meta.get_field("groups")
user_groups_query = "group__%s" % user_groups_field.related_query_name()
user_groups_field = get_user_model()._meta.get_field('groups')
user_groups_query = 'group__%s' % user_groups_field.related_query_name()
return Permission.objects.filter(**{user_groups_query: user_obj})
def _get_permissions(self, user_obj, obj, from_name):
@@ -73,16 +73,14 @@ class ModelBackend(BaseBackend):
if not user_obj.is_active or user_obj.is_anonymous or obj is not None:
return set()
perm_cache_name = "_%s_perm_cache" % from_name
perm_cache_name = '_%s_perm_cache' % from_name
if not hasattr(user_obj, perm_cache_name):
if user_obj.is_superuser:
perms = Permission.objects.all()
else:
perms = getattr(self, "_get_%s_permissions" % from_name)(user_obj)
perms = perms.values_list("content_type__app_label", "codename").order_by()
setattr(
user_obj, perm_cache_name, {"%s.%s" % (ct, name) for ct, name in perms}
)
perms = getattr(self, '_get_%s_permissions' % from_name)(user_obj)
perms = perms.values_list('content_type__app_label', 'codename').order_by()
setattr(user_obj, perm_cache_name, {"%s.%s" % (ct, name) for ct, name in perms})
return getattr(user_obj, perm_cache_name)
def get_user_permissions(self, user_obj, obj=None):
@@ -90,19 +88,19 @@ class ModelBackend(BaseBackend):
Return a set of permission strings the user `user_obj` has from their
`user_permissions`.
"""
return self._get_permissions(user_obj, obj, "user")
return self._get_permissions(user_obj, obj, 'user')
def get_group_permissions(self, user_obj, obj=None):
"""
Return a set of permission strings the user `user_obj` has from the
groups they belong.
"""
return self._get_permissions(user_obj, obj, "group")
return self._get_permissions(user_obj, obj, 'group')
def get_all_permissions(self, user_obj, obj=None):
if not user_obj.is_active or user_obj.is_anonymous or obj is not None:
return set()
if not hasattr(user_obj, "_perm_cache"):
if not hasattr(user_obj, '_perm_cache'):
user_obj._perm_cache = super().get_all_permissions(user_obj)
return user_obj._perm_cache
@@ -114,7 +112,7 @@ class ModelBackend(BaseBackend):
Return True if user_obj has any permissions in the given app_label.
"""
return user_obj.is_active and any(
perm[: perm.index(".")] == app_label
perm[:perm.index('.')] == app_label
for perm in self.get_all_permissions(user_obj)
)
@@ -125,21 +123,22 @@ class ModelBackend(BaseBackend):
"""
if isinstance(perm, str):
try:
app_label, codename = perm.split(".")
app_label, codename = perm.split('.')
except ValueError:
raise ValueError(
"Permission name should be in the form "
"app_label.permission_codename."
'Permission name should be in the form '
'app_label.permission_codename.'
)
elif not isinstance(perm, Permission):
raise TypeError(
"The `perm` argument must be a string or a permission instance."
'The `perm` argument must be a string or a permission instance.'
)
UserModel = get_user_model()
if obj is not None:
return UserModel._default_manager.none()
permission_q = Q(group__user=OuterRef("pk")) | Q(user=OuterRef("pk"))
permission_q = Q(group__user=OuterRef('pk')) | Q(user=OuterRef('pk'))
if isinstance(perm, Permission):
permission_q &= Q(pk=perm.pk)
else:
@@ -199,9 +198,9 @@ class RemoteUserBackend(ModelBackend):
# instead we use get_or_create when creating unknown users since it has
# built-in safeguards for multiple threads.
if self.create_unknown_user:
user, created = UserModel._default_manager.get_or_create(
**{UserModel.USERNAME_FIELD: username}
)
user, created = UserModel._default_manager.get_or_create(**{
UserModel.USERNAME_FIELD: username
})
if created:
user = self.configure_user(request, user)
else:
@@ -4,11 +4,10 @@ not in INSTALLED_APPS.
"""
import unicodedata
from django.conf import settings
from django.contrib.auth import password_validation
from django.contrib.auth.hashers import (
check_password,
is_password_usable,
make_password,
check_password, is_password_usable, make_password,
)
from django.db import models
from django.utils.crypto import get_random_string, salted_hmac
@@ -16,25 +15,25 @@ from django.utils.translation import gettext_lazy as _
class BaseUserManager(models.Manager):
@classmethod
def normalize_email(cls, email):
"""
Normalize the email address by lowercasing the domain part of it.
"""
email = email or ""
email = email or ''
try:
email_name, domain_part = email.strip().rsplit("@", 1)
email_name, domain_part = email.strip().rsplit('@', 1)
except ValueError:
pass
else:
email = email_name + "@" + domain_part.lower()
email = email_name + '@' + domain_part.lower()
return email
def make_random_password(
self,
length=10,
allowed_chars="abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789",
):
def make_random_password(self, length=10,
allowed_chars='abcdefghjkmnpqrstuvwxyz'
'ABCDEFGHJKLMNPQRSTUVWXYZ'
'23456789'):
"""
Generate a random password with the given length and given
allowed_chars. The default value of allowed_chars does not have "I" or
@@ -47,8 +46,8 @@ class BaseUserManager(models.Manager):
class AbstractBaseUser(models.Model):
password = models.CharField(_("password"), max_length=128)
last_login = models.DateTimeField(_("last login"), blank=True, null=True)
password = models.CharField(_('password'), max_length=128)
last_login = models.DateTimeField(_('last login'), blank=True, null=True)
is_active = True
@@ -105,13 +104,11 @@ class AbstractBaseUser(models.Model):
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):
@@ -124,6 +121,11 @@ class AbstractBaseUser(models.Model):
"""
return is_password_usable(self.password)
def _legacy_get_session_auth_hash(self):
# RemovedInDjango40Warning: pre-Django 3.1 hashes will be invalid.
key_salt = 'django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash'
return salted_hmac(key_salt, self.password, algorithm='sha1').hexdigest()
def get_session_auth_hash(self):
"""
Return an HMAC of the password field.
@@ -132,7 +134,10 @@ class AbstractBaseUser(models.Model):
return salted_hmac(
key_salt,
self.password,
algorithm="sha256",
# RemovedInDjango40Warning: when the deprecation ends, replace
# with:
# algorithm='sha256',
algorithm=settings.DEFAULT_HASHING_ALGORITHM,
).hexdigest()
@classmethod
@@ -140,12 +145,8 @@ class AbstractBaseUser(models.Model):
try:
return cls.EMAIL_FIELD
except AttributeError:
return "email"
return 'email'
@classmethod
def normalize_username(cls, username):
return (
unicodedata.normalize("NFKC", username)
if isinstance(username, str)
else username
)
return unicodedata.normalize('NFKC', username) if isinstance(username, str) else username
@@ -12,7 +12,7 @@ def check_user_model(app_configs=None, **kwargs):
if app_configs is None:
cls = apps.get_model(settings.AUTH_USER_MODEL)
else:
app_label, model_name = settings.AUTH_USER_MODEL.split(".")
app_label, model_name = settings.AUTH_USER_MODEL.split('.')
for app_config in app_configs:
if app_config.label == app_label:
cls = app_config.get_model(model_name)
@@ -31,7 +31,7 @@ def check_user_model(app_configs=None, **kwargs):
checks.Error(
"'REQUIRED_FIELDS' must be a list or tuple.",
obj=cls,
id="auth.E001",
id='auth.E001',
)
)
@@ -47,7 +47,7 @@ def check_user_model(app_configs=None, **kwargs):
% (cls.USERNAME_FIELD, cls.USERNAME_FIELD)
),
obj=cls,
id="auth.E002",
id='auth.E002',
)
)
@@ -56,49 +56,47 @@ def check_user_model(app_configs=None, **kwargs):
constraint.fields == (cls.USERNAME_FIELD,)
for constraint in cls._meta.total_unique_constraints
):
if settings.AUTHENTICATION_BACKENDS == [
"django.contrib.auth.backends.ModelBackend"
]:
if (settings.AUTHENTICATION_BACKENDS ==
['django.contrib.auth.backends.ModelBackend']):
errors.append(
checks.Error(
"'%s.%s' must be unique because it is named as the "
"'USERNAME_FIELD'." % (cls._meta.object_name, cls.USERNAME_FIELD),
"'%s.%s' must be unique because it is named as the 'USERNAME_FIELD'." % (
cls._meta.object_name, cls.USERNAME_FIELD
),
obj=cls,
id="auth.E003",
id='auth.E003',
)
)
else:
errors.append(
checks.Warning(
"'%s.%s' is named as the 'USERNAME_FIELD', but it is not unique."
% (cls._meta.object_name, cls.USERNAME_FIELD),
hint=(
"Ensure that your authentication backend(s) can handle "
"non-unique usernames."
"'%s.%s' is named as the 'USERNAME_FIELD', but it is not unique." % (
cls._meta.object_name, cls.USERNAME_FIELD
),
hint='Ensure that your authentication backend(s) can handle non-unique usernames.',
obj=cls,
id="auth.W004",
id='auth.W004',
)
)
if isinstance(cls().is_anonymous, MethodType):
errors.append(
checks.Critical(
"%s.is_anonymous must be an attribute or property rather than "
"a method. Ignoring this is a security issue as anonymous "
"users will be treated as authenticated!" % cls,
'%s.is_anonymous must be an attribute or property rather than '
'a method. Ignoring this is a security issue as anonymous '
'users will be treated as authenticated!' % cls,
obj=cls,
id="auth.C009",
id='auth.C009',
)
)
if isinstance(cls().is_authenticated, MethodType):
errors.append(
checks.Critical(
"%s.is_authenticated must be an attribute or property rather "
"than a method. Ignoring this is a security issue as anonymous "
"users will be treated as authenticated!" % cls,
'%s.is_authenticated must be an attribute or property rather '
'than a method. Ignoring this is a security issue as anonymous '
'users will be treated as authenticated!' % cls,
obj=cls,
id="auth.C010",
id='auth.C010',
)
)
return errors
@@ -108,13 +106,11 @@ def check_models_permissions(app_configs=None, **kwargs):
if app_configs is None:
models = apps.get_models()
else:
models = chain.from_iterable(
app_config.get_models() for app_config in app_configs
)
models = chain.from_iterable(app_config.get_models() for app_config in app_configs)
Permission = apps.get_model("auth", "Permission")
permission_name_max_length = Permission._meta.get_field("name").max_length
permission_codename_max_length = Permission._meta.get_field("codename").max_length
Permission = apps.get_model('auth', 'Permission')
permission_name_max_length = Permission._meta.get_field('name').max_length
permission_codename_max_length = Permission._meta.get_field('codename').max_length
errors = []
for model in models:
@@ -123,28 +119,27 @@ def check_models_permissions(app_configs=None, **kwargs):
# Check builtin permission name length.
max_builtin_permission_name_length = (
max(len(name) for name in builtin_permissions.values())
if builtin_permissions
else 0
if builtin_permissions else 0
)
if max_builtin_permission_name_length > permission_name_max_length:
verbose_name_max_length = permission_name_max_length - (
max_builtin_permission_name_length - len(opts.verbose_name_raw)
verbose_name_max_length = (
permission_name_max_length - (max_builtin_permission_name_length - len(opts.verbose_name_raw))
)
errors.append(
checks.Error(
"The verbose_name of model '%s' must be at most %d "
"characters for its builtin permission names to be at "
"most %d characters."
% (opts.label, verbose_name_max_length, permission_name_max_length),
"most %d characters." % (
opts.label, verbose_name_max_length, permission_name_max_length
),
obj=model,
id="auth.E007",
id='auth.E007',
)
)
# Check builtin permission codename length.
max_builtin_permission_codename_length = (
max(len(codename) for codename in builtin_permissions.keys())
if builtin_permissions
else 0
if builtin_permissions else 0
)
if max_builtin_permission_codename_length > permission_codename_max_length:
model_name_max_length = permission_codename_max_length - (
@@ -154,14 +149,13 @@ def check_models_permissions(app_configs=None, **kwargs):
checks.Error(
"The name of model '%s' must be at most %d characters "
"for its builtin permission codenames to be at most %d "
"characters."
% (
"characters." % (
opts.label,
model_name_max_length,
permission_codename_max_length,
),
obj=model,
id="auth.E011",
id='auth.E011',
)
)
codenames = set()
@@ -171,14 +165,11 @@ def check_models_permissions(app_configs=None, **kwargs):
errors.append(
checks.Error(
"The permission named '%s' of model '%s' is longer "
"than %d characters."
% (
name,
opts.label,
permission_name_max_length,
"than %d characters." % (
name, opts.label, permission_name_max_length,
),
obj=model,
id="auth.E008",
id='auth.E008',
)
)
# Check custom permission codename length.
@@ -186,24 +177,23 @@ def check_models_permissions(app_configs=None, **kwargs):
errors.append(
checks.Error(
"The permission codenamed '%s' of model '%s' is "
"longer than %d characters."
% (
"longer than %d characters." % (
codename,
opts.label,
permission_codename_max_length,
),
obj=model,
id="auth.E012",
id='auth.E012',
)
)
# Check custom permissions codename clashing.
if codename in builtin_permissions:
errors.append(
checks.Error(
"The permission codenamed '%s' clashes with a builtin "
"permission for model '%s'." % (codename, opts.label),
"The permission codenamed '%s' clashes with a builtin permission "
"for model '%s'." % (codename, opts.label),
obj=model,
id="auth.E005",
id='auth.E005',
)
)
elif codename in codenames:
@@ -212,7 +202,7 @@ def check_models_permissions(app_configs=None, **kwargs):
"The permission codenamed '%s' is duplicated for "
"model '%s'." % (codename, opts.label),
obj=model,
id="auth.E006",
id='auth.E006',
)
)
codenames.add(codename)
@@ -25,9 +25,6 @@ class PermWrapper:
def __init__(self, user):
self.user = user
def __repr__(self):
return f"{self.__class__.__qualname__}({self.user!r})"
def __getitem__(self, app_label):
return PermLookupDict(self.user, app_label)
@@ -39,10 +36,10 @@ class PermWrapper:
"""
Lookup by "someapp" or "someapp.someperm" in perms.
"""
if "." not in perm_name:
if '.' not in perm_name:
# The name refers to module.
return bool(self[perm_name])
app_label, perm_name = perm_name.split(".", 1)
app_label, perm_name = perm_name.split('.', 1)
return self[app_label][perm_name]
@@ -54,14 +51,13 @@ def auth(request):
If there is no 'user' attribute in the request, use AnonymousUser (from
django.contrib.auth).
"""
if hasattr(request, "user"):
if hasattr(request, 'user'):
user = request.user
else:
from django.contrib.auth.models import AnonymousUser
user = AnonymousUser()
return {
"user": user,
"perms": PermWrapper(user),
'user': user,
'perms': PermWrapper(user),
}
@@ -7,9 +7,7 @@ from django.core.exceptions import PermissionDenied
from django.shortcuts import resolve_url
def user_passes_test(
test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME
):
def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
"""
Decorator for views that checks that the user passes the given test,
redirecting to the log-in page if necessary. The test should be a callable
@@ -27,22 +25,17 @@ def user_passes_test(
# use the path as the "next" url.
login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
current_scheme, current_netloc = urlparse(path)[:2]
if (not login_scheme or login_scheme == current_scheme) and (
not login_netloc or login_netloc == current_netloc
):
if ((not login_scheme or login_scheme == current_scheme) and
(not login_netloc or login_netloc == current_netloc)):
path = request.get_full_path()
from django.contrib.auth.views import redirect_to_login
return redirect_to_login(path, resolved_login_url, redirect_field_name)
return redirect_to_login(
path, resolved_login_url, redirect_field_name)
return _wrapped_view
return decorator
def login_required(
function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None
):
def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
"""
Decorator for views that checks that the user is logged in, redirecting
to the log-in page if necessary.
@@ -50,7 +43,7 @@ def login_required(
actual_decorator = user_passes_test(
lambda u: u.is_authenticated,
login_url=login_url,
redirect_field_name=redirect_field_name,
redirect_field_name=redirect_field_name
)
if function:
return actual_decorator(function)
@@ -64,7 +57,6 @@ def permission_required(perm, login_url=None, raise_exception=False):
If the raise_exception parameter is given the PermissionDenied exception
is raised.
"""
def check_perms(user):
if isinstance(perm, str):
perms = (perm,)
@@ -78,5 +70,4 @@ def permission_required(perm, login_url=None, raise_exception=False):
raise PermissionDenied
# As the last resort, show the login form
return False
return user_passes_test(check_perms, login_url=login_url)
@@ -1,8 +1,12 @@
import unicodedata
from django import forms
from django.contrib.auth import authenticate, get_user_model, password_validation
from django.contrib.auth.hashers import UNUSABLE_PASSWORD_PREFIX, identify_hasher
from django.contrib.auth import (
authenticate, get_user_model, password_validation,
)
from django.contrib.auth.hashers import (
UNUSABLE_PASSWORD_PREFIX, identify_hasher,
)
from django.contrib.auth.models import User
from django.contrib.auth.tokens import default_token_generator
from django.contrib.sites.shortcuts import get_current_site
@@ -12,8 +16,7 @@ from django.template import loader
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
from django.utils.text import capfirst
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext, gettext_lazy as _
UserModel = get_user_model()
@@ -24,60 +27,48 @@ def _unicode_ci_compare(s1, s2):
recommended algorithm from Unicode Technical Report 36, section
2.11.2(B)(2).
"""
return (
unicodedata.normalize("NFKC", s1).casefold()
== unicodedata.normalize("NFKC", s2).casefold()
)
return unicodedata.normalize('NFKC', s1).casefold() == unicodedata.normalize('NFKC', s2).casefold()
class ReadOnlyPasswordHashWidget(forms.Widget):
template_name = "auth/widgets/read_only_password_hash.html"
template_name = 'auth/widgets/read_only_password_hash.html'
read_only = True
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
summary = []
if not value or value.startswith(UNUSABLE_PASSWORD_PREFIX):
summary.append({"label": gettext("No password set.")})
summary.append({'label': gettext("No password set.")})
else:
try:
hasher = identify_hasher(value)
except ValueError:
summary.append(
{
"label": gettext(
"Invalid password format or unknown hashing algorithm."
)
}
)
summary.append({'label': gettext("Invalid password format or unknown hashing algorithm.")})
else:
for key, value_ in hasher.safe_summary(value).items():
summary.append({"label": gettext(key), "value": value_})
context["summary"] = summary
summary.append({'label': gettext(key), 'value': value_})
context['summary'] = summary
return context
def id_for_label(self, id_):
return None
class ReadOnlyPasswordHashField(forms.Field):
widget = ReadOnlyPasswordHashWidget
def __init__(self, *args, **kwargs):
kwargs.setdefault("required", False)
kwargs.setdefault("disabled", True)
kwargs.setdefault('disabled', True)
super().__init__(*args, **kwargs)
class UsernameField(forms.CharField):
def to_python(self, value):
return unicodedata.normalize("NFKC", super().to_python(value))
return unicodedata.normalize('NFKC', super().to_python(value))
def widget_attrs(self, widget):
return {
**super().widget_attrs(widget),
"autocapitalize": "none",
"autocomplete": "username",
'autocapitalize': 'none',
'autocomplete': 'username',
}
@@ -86,19 +77,18 @@ class UserCreationForm(forms.ModelForm):
A form that creates a user, with no privileges, from the given username and
password.
"""
error_messages = {
"password_mismatch": _("The two password fields didnt match."),
'password_mismatch': _('The two password fields didnt match.'),
}
password1 = forms.CharField(
label=_("Password"),
strip=False,
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
help_text=password_validation.password_validators_help_text_html(),
)
password2 = forms.CharField(
label=_("Password confirmation"),
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
strip=False,
help_text=_("Enter the same password as before, for verification."),
)
@@ -106,22 +96,20 @@ class UserCreationForm(forms.ModelForm):
class Meta:
model = User
fields = ("username",)
field_classes = {"username": UsernameField}
field_classes = {'username': UsernameField}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self._meta.model.USERNAME_FIELD in self.fields:
self.fields[self._meta.model.USERNAME_FIELD].widget.attrs[
"autofocus"
] = True
self.fields[self._meta.model.USERNAME_FIELD].widget.attrs['autofocus'] = True
def clean_password2(self):
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise ValidationError(
self.error_messages["password_mismatch"],
code="password_mismatch",
self.error_messages['password_mismatch'],
code='password_mismatch',
)
return password2
@@ -129,12 +117,12 @@ class UserCreationForm(forms.ModelForm):
super()._post_clean()
# Validate the password after self.instance is updated with form data
# by super().
password = self.cleaned_data.get("password2")
password = self.cleaned_data.get('password2')
if password:
try:
password_validation.validate_password(password, self.instance)
except ValidationError as error:
self.add_error("password2", error)
self.add_error('password2', error)
def save(self, commit=True):
user = super().save(commit=False)
@@ -148,27 +136,25 @@ class UserChangeForm(forms.ModelForm):
password = ReadOnlyPasswordHashField(
label=_("Password"),
help_text=_(
"Raw passwords are not stored, so there is no way to see this "
"users password, but you can change the password using "
'Raw passwords are not stored, so there is no way to see this '
'users password, but you can change the password using '
'<a href="{}">this form</a>.'
),
)
class Meta:
model = User
fields = "__all__"
field_classes = {"username": UsernameField}
fields = '__all__'
field_classes = {'username': UsernameField}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
password = self.fields.get("password")
password = self.fields.get('password')
if password:
password.help_text = password.help_text.format("../password/")
user_permissions = self.fields.get("user_permissions")
password.help_text = password.help_text.format('../password/')
user_permissions = self.fields.get('user_permissions')
if user_permissions:
user_permissions.queryset = user_permissions.queryset.select_related(
"content_type"
)
user_permissions.queryset = user_permissions.queryset.select_related('content_type')
class AuthenticationForm(forms.Form):
@@ -176,20 +162,19 @@ class AuthenticationForm(forms.Form):
Base class for authenticating users. Extend this to get a form that accepts
username/password logins.
"""
username = UsernameField(widget=forms.TextInput(attrs={"autofocus": True}))
username = UsernameField(widget=forms.TextInput(attrs={'autofocus': True}))
password = forms.CharField(
label=_("Password"),
strip=False,
widget=forms.PasswordInput(attrs={"autocomplete": "current-password"}),
widget=forms.PasswordInput(attrs={'autocomplete': 'current-password'}),
)
error_messages = {
"invalid_login": _(
'invalid_login': _(
"Please enter a correct %(username)s and password. Note that both "
"fields may be case-sensitive."
),
"inactive": _("This account is inactive."),
'inactive': _("This account is inactive."),
}
def __init__(self, request=None, *args, **kwargs):
@@ -204,19 +189,17 @@ class AuthenticationForm(forms.Form):
# Set the max length and label for the "username" field.
self.username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
username_max_length = self.username_field.max_length or 254
self.fields["username"].max_length = username_max_length
self.fields["username"].widget.attrs["maxlength"] = username_max_length
if self.fields["username"].label is None:
self.fields["username"].label = capfirst(self.username_field.verbose_name)
self.fields['username'].max_length = username_max_length
self.fields['username'].widget.attrs['maxlength'] = username_max_length
if self.fields['username'].label is None:
self.fields['username'].label = capfirst(self.username_field.verbose_name)
def clean(self):
username = self.cleaned_data.get("username")
password = self.cleaned_data.get("password")
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
if username is not None and password:
self.user_cache = authenticate(
self.request, username=username, password=password
)
self.user_cache = authenticate(self.request, username=username, password=password)
if self.user_cache is None:
raise self.get_invalid_login_error()
else:
@@ -237,8 +220,8 @@ class AuthenticationForm(forms.Form):
"""
if not user.is_active:
raise ValidationError(
self.error_messages["inactive"],
code="inactive",
self.error_messages['inactive'],
code='inactive',
)
def get_user(self):
@@ -246,9 +229,9 @@ class AuthenticationForm(forms.Form):
def get_invalid_login_error(self):
return ValidationError(
self.error_messages["invalid_login"],
code="invalid_login",
params={"username": self.username_field.verbose_name},
self.error_messages['invalid_login'],
code='invalid_login',
params={'username': self.username_field.verbose_name},
)
@@ -256,30 +239,23 @@ class PasswordResetForm(forms.Form):
email = forms.EmailField(
label=_("Email"),
max_length=254,
widget=forms.EmailInput(attrs={"autocomplete": "email"}),
widget=forms.EmailInput(attrs={'autocomplete': 'email'})
)
def send_mail(
self,
subject_template_name,
email_template_name,
context,
from_email,
to_email,
html_email_template_name=None,
):
def send_mail(self, subject_template_name, email_template_name,
context, from_email, to_email, html_email_template_name=None):
"""
Send a django.core.mail.EmailMultiAlternatives to `to_email`.
"""
subject = loader.render_to_string(subject_template_name, context)
# Email subject *must not* contain newlines
subject = "".join(subject.splitlines())
subject = ''.join(subject.splitlines())
body = loader.render_to_string(email_template_name, context)
email_message = EmailMultiAlternatives(subject, body, from_email, [to_email])
if html_email_template_name is not None:
html_email = loader.render_to_string(html_email_template_name, context)
email_message.attach_alternative(html_email, "text/html")
email_message.attach_alternative(html_email, 'text/html')
email_message.send()
@@ -291,31 +267,22 @@ class PasswordResetForm(forms.Form):
resetting their password.
"""
email_field_name = UserModel.get_email_field_name()
active_users = UserModel._default_manager.filter(
**{
"%s__iexact" % email_field_name: email,
"is_active": True,
}
)
active_users = UserModel._default_manager.filter(**{
'%s__iexact' % email_field_name: email,
'is_active': True,
})
return (
u
for u in active_users
if u.has_usable_password()
and _unicode_ci_compare(email, getattr(u, email_field_name))
u for u in active_users
if u.has_usable_password() and
_unicode_ci_compare(email, getattr(u, email_field_name))
)
def save(
self,
domain_override=None,
subject_template_name="registration/password_reset_subject.txt",
email_template_name="registration/password_reset_email.html",
use_https=False,
token_generator=default_token_generator,
from_email=None,
request=None,
html_email_template_name=None,
extra_email_context=None,
):
def save(self, domain_override=None,
subject_template_name='registration/password_reset_subject.txt',
email_template_name='registration/password_reset_email.html',
use_https=False, token_generator=default_token_generator,
from_email=None, request=None, html_email_template_name=None,
extra_email_context=None):
"""
Generate a one-use only link for resetting password and send it to the
user.
@@ -331,22 +298,18 @@ class PasswordResetForm(forms.Form):
for user in self.get_users(email):
user_email = getattr(user, email_field_name)
context = {
"email": user_email,
"domain": domain,
"site_name": site_name,
"uid": urlsafe_base64_encode(force_bytes(user.pk)),
"user": user,
"token": token_generator.make_token(user),
"protocol": "https" if use_https else "http",
'email': user_email,
'domain': domain,
'site_name': site_name,
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'user': user,
'token': token_generator.make_token(user),
'protocol': 'https' if use_https else 'http',
**(extra_email_context or {}),
}
self.send_mail(
subject_template_name,
email_template_name,
context,
from_email,
user_email,
html_email_template_name=html_email_template_name,
subject_template_name, email_template_name, context, from_email,
user_email, html_email_template_name=html_email_template_name,
)
@@ -355,20 +318,19 @@ class SetPasswordForm(forms.Form):
A form that lets a user change set their password without entering the old
password
"""
error_messages = {
"password_mismatch": _("The two password fields didnt match."),
'password_mismatch': _('The two password fields didnt match.'),
}
new_password1 = forms.CharField(
label=_("New password"),
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
strip=False,
help_text=password_validation.password_validators_help_text_html(),
)
new_password2 = forms.CharField(
label=_("New password confirmation"),
strip=False,
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
)
def __init__(self, user, *args, **kwargs):
@@ -376,13 +338,13 @@ class SetPasswordForm(forms.Form):
super().__init__(*args, **kwargs)
def clean_new_password2(self):
password1 = self.cleaned_data.get("new_password1")
password2 = self.cleaned_data.get("new_password2")
password1 = self.cleaned_data.get('new_password1')
password2 = self.cleaned_data.get('new_password2')
if password1 and password2:
if password1 != password2:
raise ValidationError(
self.error_messages["password_mismatch"],
code="password_mismatch",
self.error_messages['password_mismatch'],
code='password_mismatch',
)
password_validation.validate_password(password2, self.user)
return password2
@@ -400,22 +362,17 @@ class PasswordChangeForm(SetPasswordForm):
A form that lets a user change their password by entering their old
password.
"""
error_messages = {
**SetPasswordForm.error_messages,
"password_incorrect": _(
"Your old password was entered incorrectly. Please enter it again."
),
'password_incorrect': _("Your old password was entered incorrectly. Please enter it again."),
}
old_password = forms.CharField(
label=_("Old password"),
strip=False,
widget=forms.PasswordInput(
attrs={"autocomplete": "current-password", "autofocus": True}
),
widget=forms.PasswordInput(attrs={'autocomplete': 'current-password', 'autofocus': True}),
)
field_order = ["old_password", "new_password1", "new_password2"]
field_order = ['old_password', 'new_password1', 'new_password2']
def clean_old_password(self):
"""
@@ -424,8 +381,8 @@ class PasswordChangeForm(SetPasswordForm):
old_password = self.cleaned_data["old_password"]
if not self.user.check_password(old_password):
raise ValidationError(
self.error_messages["password_incorrect"],
code="password_incorrect",
self.error_messages['password_incorrect'],
code='password_incorrect',
)
return old_password
@@ -434,22 +391,19 @@ class AdminPasswordChangeForm(forms.Form):
"""
A form used to change the password of a user in the admin interface.
"""
error_messages = {
"password_mismatch": _("The two password fields didnt match."),
'password_mismatch': _('The two password fields didnt match.'),
}
required_css_class = "required"
required_css_class = 'required'
password1 = forms.CharField(
label=_("Password"),
widget=forms.PasswordInput(
attrs={"autocomplete": "new-password", "autofocus": True}
),
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password', 'autofocus': True}),
strip=False,
help_text=password_validation.password_validators_help_text_html(),
)
password2 = forms.CharField(
label=_("Password (again)"),
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
strip=False,
help_text=_("Enter the same password as before, for verification."),
)
@@ -459,12 +413,12 @@ class AdminPasswordChangeForm(forms.Form):
super().__init__(*args, **kwargs)
def clean_password2(self):
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
password1 = self.cleaned_data.get('password1')
password2 = self.cleaned_data.get('password2')
if password1 and password2 and password1 != password2:
raise ValidationError(
self.error_messages["password_mismatch"],
code="password_mismatch",
self.error_messages['password_mismatch'],
code='password_mismatch',
)
password_validation.validate_password(password2, self.user)
return password2
@@ -483,4 +437,4 @@ class AdminPasswordChangeForm(forms.Form):
for name in self.fields:
if name not in data:
return []
return ["password"]
return ['password']
@@ -11,18 +11,13 @@ from django.core.exceptions import ImproperlyConfigured
from django.core.signals import setting_changed
from django.dispatch import receiver
from django.utils.crypto import (
RANDOM_STRING_CHARS,
constant_time_compare,
get_random_string,
pbkdf2,
RANDOM_STRING_CHARS, constant_time_compare, get_random_string, pbkdf2,
)
from django.utils.module_loading import import_string
from django.utils.translation import gettext_noop as _
UNUSABLE_PASSWORD_PREFIX = "!" # This will never be a valid encoded hash
UNUSABLE_PASSWORD_SUFFIX_LENGTH = (
40 # number of random chars to add after UNUSABLE_PASSWORD_PREFIX
)
UNUSABLE_PASSWORD_PREFIX = '!' # This will never be a valid encoded hash
UNUSABLE_PASSWORD_SUFFIX_LENGTH = 40 # number of random chars to add after UNUSABLE_PASSWORD_PREFIX
def is_password_usable(encoded):
@@ -33,7 +28,7 @@ def is_password_usable(encoded):
return encoded is None or not encoded.startswith(UNUSABLE_PASSWORD_PREFIX)
def check_password(password, encoded, setter=None, preferred="default"):
def check_password(password, encoded, setter=None, preferred='default'):
"""
Return a boolean of whether the raw password matches the three
part encoded digest.
@@ -67,7 +62,7 @@ def check_password(password, encoded, setter=None, preferred="default"):
return is_correct
def make_password(password, salt=None, hasher="default"):
def make_password(password, salt=None, hasher='default'):
"""
Turn a plain-text password into a hash for database storage
@@ -77,12 +72,11 @@ def make_password(password, salt=None, hasher="default"):
access to staff or superuser accounts. See ticket #20079 for more info.
"""
if password is None:
return UNUSABLE_PASSWORD_PREFIX + get_random_string(
UNUSABLE_PASSWORD_SUFFIX_LENGTH
)
return UNUSABLE_PASSWORD_PREFIX + get_random_string(UNUSABLE_PASSWORD_SUFFIX_LENGTH)
if not isinstance(password, (bytes, str)):
raise TypeError(
"Password must be a string or bytes, got %s." % type(password).__qualname__
'Password must be a string or bytes, got %s.'
% type(password).__qualname__
)
hasher = get_hasher(hasher)
salt = salt or hasher.salt()
@@ -95,10 +89,9 @@ def get_hashers():
for hasher_path in settings.PASSWORD_HASHERS:
hasher_cls = import_string(hasher_path)
hasher = hasher_cls()
if not getattr(hasher, "algorithm"):
raise ImproperlyConfigured(
"hasher doesn't specify an algorithm name: %s" % hasher_path
)
if not getattr(hasher, 'algorithm'):
raise ImproperlyConfigured("hasher doesn't specify an "
"algorithm name: %s" % hasher_path)
hashers.append(hasher)
return hashers
@@ -110,22 +103,22 @@ def get_hashers_by_algorithm():
@receiver(setting_changed)
def reset_hashers(**kwargs):
if kwargs["setting"] == "PASSWORD_HASHERS":
if kwargs['setting'] == 'PASSWORD_HASHERS':
get_hashers.cache_clear()
get_hashers_by_algorithm.cache_clear()
def get_hasher(algorithm="default"):
def get_hasher(algorithm='default'):
"""
Return an instance of a loaded password hasher.
If algorithm is 'default', return the default hasher. Lazily import hashers
specified in the project's settings file if needed.
"""
if hasattr(algorithm, "algorithm"):
if hasattr(algorithm, 'algorithm'):
return algorithm
elif algorithm == "default":
elif algorithm == 'default':
return get_hashers()[0]
else:
@@ -133,11 +126,9 @@ def get_hasher(algorithm="default"):
try:
return hashers[algorithm]
except KeyError:
raise ValueError(
"Unknown password hashing algorithm '%s'. "
"Did you specify it in the PASSWORD_HASHERS "
"setting?" % algorithm
)
raise ValueError("Unknown password hashing algorithm '%s'. "
"Did you specify it in the PASSWORD_HASHERS "
"setting?" % algorithm)
def identify_hasher(encoded):
@@ -150,15 +141,14 @@ def identify_hasher(encoded):
"""
# Ancient versions of Django created plain MD5 passwords and accepted
# MD5 passwords with an empty salt.
if (len(encoded) == 32 and "$" not in encoded) or (
len(encoded) == 37 and encoded.startswith("md5$$")
):
algorithm = "unsalted_md5"
if ((len(encoded) == 32 and '$' not in encoded) or
(len(encoded) == 37 and encoded.startswith('md5$$'))):
algorithm = 'unsalted_md5'
# Ancient versions of Django accepted SHA1 passwords with an empty salt.
elif len(encoded) == 46 and encoded.startswith("sha1$$"):
algorithm = "unsalted_sha1"
elif len(encoded) == 46 and encoded.startswith('sha1$$'):
algorithm = 'unsalted_sha1'
else:
algorithm = encoded.split("$", 1)[0]
algorithm = encoded.split('$', 1)[0]
return get_hasher(algorithm)
@@ -186,7 +176,6 @@ class BasePasswordHasher:
PasswordHasher objects are immutable.
"""
algorithm = None
library = None
salt_entropy = 128
@@ -200,14 +189,11 @@ class BasePasswordHasher:
try:
module = importlib.import_module(mod_path)
except ImportError as e:
raise ValueError(
"Couldn't load %r algorithm library: %s"
% (self.__class__.__name__, e)
)
raise ValueError("Couldn't load %r algorithm library: %s" %
(self.__class__.__name__, e))
return module
raise ValueError(
"Hasher %r doesn't specify a library attribute" % self.__class__.__name__
)
raise ValueError("Hasher %r doesn't specify a library attribute" %
self.__class__.__name__)
def salt(self):
"""
@@ -221,15 +207,7 @@ class BasePasswordHasher:
def verify(self, password, encoded):
"""Check if the given password is correct."""
raise NotImplementedError(
"subclasses of BasePasswordHasher must provide a verify() method"
)
def _check_encode_args(self, password, salt):
if password is None:
raise TypeError("password must be provided.")
if not salt or "$" in salt:
raise ValueError("salt must be provided and cannot contain $.")
raise NotImplementedError('subclasses of BasePasswordHasher must provide a verify() method')
def encode(self, password, salt):
"""
@@ -238,9 +216,7 @@ class BasePasswordHasher:
The result is normally formatted as "algorithm$salt$hash" and
must be fewer than 128 characters.
"""
raise NotImplementedError(
"subclasses of BasePasswordHasher must provide an encode() method"
)
raise NotImplementedError('subclasses of BasePasswordHasher must provide an encode() method')
def decode(self, encoded):
"""
@@ -251,7 +227,7 @@ class BasePasswordHasher:
`work_factor`.
"""
raise NotImplementedError(
"subclasses of BasePasswordHasher must provide a decode() method."
'subclasses of BasePasswordHasher must provide a decode() method.'
)
def safe_summary(self, encoded):
@@ -261,9 +237,7 @@ class BasePasswordHasher:
The result is a dictionary and will be used where the password field
must be displayed to construct a safe representation of the password.
"""
raise NotImplementedError(
"subclasses of BasePasswordHasher must provide a safe_summary() method"
)
raise NotImplementedError('subclasses of BasePasswordHasher must provide a safe_summary() method')
def must_update(self, encoded):
return False
@@ -279,9 +253,7 @@ class BasePasswordHasher:
for any hasher that has a work factor. If not, this method should be
defined as a no-op to silence the warning.
"""
warnings.warn(
"subclasses of BasePasswordHasher should provide a harden_runtime() method"
)
warnings.warn('subclasses of BasePasswordHasher should provide a harden_runtime() method')
class PBKDF2PasswordHasher(BasePasswordHasher):
@@ -292,52 +264,52 @@ class PBKDF2PasswordHasher(BasePasswordHasher):
The result is a 64 byte binary string. Iterations may be changed
safely but you must rename the algorithm if you change SHA256.
"""
algorithm = "pbkdf2_sha256"
iterations = 320000
iterations = 260000
digest = hashlib.sha256
def encode(self, password, salt, iterations=None):
self._check_encode_args(password, salt)
assert password is not None
assert salt and '$' not in salt
iterations = iterations or self.iterations
hash = pbkdf2(password, salt, iterations, digest=self.digest)
hash = base64.b64encode(hash).decode("ascii").strip()
hash = base64.b64encode(hash).decode('ascii').strip()
return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)
def decode(self, encoded):
algorithm, iterations, salt, hash = encoded.split("$", 3)
algorithm, iterations, salt, hash = encoded.split('$', 3)
assert algorithm == self.algorithm
return {
"algorithm": algorithm,
"hash": hash,
"iterations": int(iterations),
"salt": salt,
'algorithm': algorithm,
'hash': hash,
'iterations': int(iterations),
'salt': salt,
}
def verify(self, password, encoded):
decoded = self.decode(encoded)
encoded_2 = self.encode(password, decoded["salt"], decoded["iterations"])
encoded_2 = self.encode(password, decoded['salt'], decoded['iterations'])
return constant_time_compare(encoded, encoded_2)
def safe_summary(self, encoded):
decoded = self.decode(encoded)
return {
_("algorithm"): decoded["algorithm"],
_("iterations"): decoded["iterations"],
_("salt"): mask_hash(decoded["salt"]),
_("hash"): mask_hash(decoded["hash"]),
_('algorithm'): decoded['algorithm'],
_('iterations'): decoded['iterations'],
_('salt'): mask_hash(decoded['salt']),
_('hash'): mask_hash(decoded['hash']),
}
def must_update(self, encoded):
decoded = self.decode(encoded)
update_salt = must_update_salt(decoded["salt"], self.salt_entropy)
return (decoded["iterations"] != self.iterations) or update_salt
update_salt = must_update_salt(decoded['salt'], self.salt_entropy)
return (decoded['iterations'] != self.iterations) or update_salt
def harden_runtime(self, password, encoded):
decoded = self.decode(encoded)
extra_iterations = self.iterations - decoded["iterations"]
extra_iterations = self.iterations - decoded['iterations']
if extra_iterations > 0:
self.encode(password, decoded["salt"], extra_iterations)
self.encode(password, decoded['salt'], extra_iterations)
class PBKDF2SHA1PasswordHasher(PBKDF2PasswordHasher):
@@ -347,7 +319,6 @@ class PBKDF2SHA1PasswordHasher(PBKDF2PasswordHasher):
implementations of PBKDF2, such as openssl's
PKCS5_PBKDF2_HMAC_SHA1().
"""
algorithm = "pbkdf2_sha1"
digest = hashlib.sha1
@@ -360,9 +331,8 @@ class Argon2PasswordHasher(BasePasswordHasher):
(https://password-hashing.net). It requires the argon2-cffi library which
depends on native C code and might cause portability issues.
"""
algorithm = "argon2"
library = "argon2"
algorithm = 'argon2'
library = 'argon2'
time_cost = 2
memory_cost = 102400
@@ -380,59 +350,59 @@ class Argon2PasswordHasher(BasePasswordHasher):
hash_len=params.hash_len,
type=params.type,
)
return self.algorithm + data.decode("ascii")
return self.algorithm + data.decode('ascii')
def decode(self, encoded):
argon2 = self._load_library()
algorithm, rest = encoded.split("$", 1)
algorithm, rest = encoded.split('$', 1)
assert algorithm == self.algorithm
params = argon2.extract_parameters("$" + rest)
variety, *_, b64salt, hash = rest.split("$")
params = argon2.extract_parameters('$' + rest)
variety, *_, b64salt, hash = rest.split('$')
# Add padding.
b64salt += "=" * (-len(b64salt) % 4)
salt = base64.b64decode(b64salt).decode("latin1")
b64salt += '=' * (-len(b64salt) % 4)
salt = base64.b64decode(b64salt).decode('latin1')
return {
"algorithm": algorithm,
"hash": hash,
"memory_cost": params.memory_cost,
"parallelism": params.parallelism,
"salt": salt,
"time_cost": params.time_cost,
"variety": variety,
"version": params.version,
"params": params,
'algorithm': algorithm,
'hash': hash,
'memory_cost': params.memory_cost,
'parallelism': params.parallelism,
'salt': salt,
'time_cost': params.time_cost,
'variety': variety,
'version': params.version,
'params': params,
}
def verify(self, password, encoded):
argon2 = self._load_library()
algorithm, rest = encoded.split("$", 1)
algorithm, rest = encoded.split('$', 1)
assert algorithm == self.algorithm
try:
return argon2.PasswordHasher().verify("$" + rest, password)
return argon2.PasswordHasher().verify('$' + rest, password)
except argon2.exceptions.VerificationError:
return False
def safe_summary(self, encoded):
decoded = self.decode(encoded)
return {
_("algorithm"): decoded["algorithm"],
_("variety"): decoded["variety"],
_("version"): decoded["version"],
_("memory cost"): decoded["memory_cost"],
_("time cost"): decoded["time_cost"],
_("parallelism"): decoded["parallelism"],
_("salt"): mask_hash(decoded["salt"]),
_("hash"): mask_hash(decoded["hash"]),
_('algorithm'): decoded['algorithm'],
_('variety'): decoded['variety'],
_('version'): decoded['version'],
_('memory cost'): decoded['memory_cost'],
_('time cost'): decoded['time_cost'],
_('parallelism'): decoded['parallelism'],
_('salt'): mask_hash(decoded['salt']),
_('hash'): mask_hash(decoded['hash']),
}
def must_update(self, encoded):
decoded = self.decode(encoded)
current_params = decoded["params"]
current_params = decoded['params']
new_params = self.params()
# Set salt_len to the salt_len of the current parameters because salt
# is explicitly passed to argon2.
new_params.salt_len = current_params.salt_len
update_salt = must_update_salt(decoded["salt"], self.salt_entropy)
update_salt = must_update_salt(decoded['salt'], self.salt_entropy)
return (current_params != new_params) or update_salt
def harden_runtime(self, password, encoded):
@@ -463,7 +433,6 @@ class BCryptSHA256PasswordHasher(BasePasswordHasher):
this library depends on native C code and might cause portability
issues.
"""
algorithm = "bcrypt_sha256"
digest = hashlib.sha256
library = ("bcrypt", "bcrypt")
@@ -483,46 +452,46 @@ class BCryptSHA256PasswordHasher(BasePasswordHasher):
password = binascii.hexlify(self.digest(password).digest())
data = bcrypt.hashpw(password, salt)
return "%s$%s" % (self.algorithm, data.decode("ascii"))
return "%s$%s" % (self.algorithm, data.decode('ascii'))
def decode(self, encoded):
algorithm, empty, algostr, work_factor, data = encoded.split("$", 4)
algorithm, empty, algostr, work_factor, data = encoded.split('$', 4)
assert algorithm == self.algorithm
return {
"algorithm": algorithm,
"algostr": algostr,
"checksum": data[22:],
"salt": data[:22],
"work_factor": int(work_factor),
'algorithm': algorithm,
'algostr': algostr,
'checksum': data[22:],
'salt': data[:22],
'work_factor': int(work_factor),
}
def verify(self, password, encoded):
algorithm, data = encoded.split("$", 1)
algorithm, data = encoded.split('$', 1)
assert algorithm == self.algorithm
encoded_2 = self.encode(password, data.encode("ascii"))
encoded_2 = self.encode(password, data.encode('ascii'))
return constant_time_compare(encoded, encoded_2)
def safe_summary(self, encoded):
decoded = self.decode(encoded)
return {
_("algorithm"): decoded["algorithm"],
_("work factor"): decoded["work_factor"],
_("salt"): mask_hash(decoded["salt"]),
_("checksum"): mask_hash(decoded["checksum"]),
_('algorithm'): decoded['algorithm'],
_('work factor'): decoded['work_factor'],
_('salt'): mask_hash(decoded['salt']),
_('checksum'): mask_hash(decoded['checksum']),
}
def must_update(self, encoded):
decoded = self.decode(encoded)
return decoded["work_factor"] != self.rounds
return decoded['work_factor'] != self.rounds
def harden_runtime(self, password, encoded):
_, data = encoded.split("$", 1)
_, data = encoded.split('$', 1)
salt = data[:29] # Length of the salt in bcrypt.
rounds = data.split("$")[2]
rounds = data.split('$')[2]
# work factor is logarithmic, adding one doubles the load.
diff = 2 ** (self.rounds - int(rounds)) - 1
diff = 2**(self.rounds - int(rounds)) - 1
while diff > 0:
self.encode(password, salt.encode("ascii"))
self.encode(password, salt.encode('ascii'))
diff -= 1
@@ -539,126 +508,47 @@ class BCryptPasswordHasher(BCryptSHA256PasswordHasher):
bcrypt's 72 bytes password truncation. Most use cases should prefer the
BCryptSHA256PasswordHasher.
"""
algorithm = "bcrypt"
digest = None
class ScryptPasswordHasher(BasePasswordHasher):
"""
Secure password hashing using the Scrypt algorithm.
"""
algorithm = "scrypt"
block_size = 8
maxmem = 0
parallelism = 1
work_factor = 2**14
def encode(self, password, salt, n=None, r=None, p=None):
self._check_encode_args(password, salt)
n = n or self.work_factor
r = r or self.block_size
p = p or self.parallelism
hash_ = hashlib.scrypt(
password.encode(),
salt=salt.encode(),
n=n,
r=r,
p=p,
maxmem=self.maxmem,
dklen=64,
)
hash_ = base64.b64encode(hash_).decode("ascii").strip()
return "%s$%d$%s$%d$%d$%s" % (self.algorithm, n, salt, r, p, hash_)
def decode(self, encoded):
algorithm, work_factor, salt, block_size, parallelism, hash_ = encoded.split(
"$", 6
)
assert algorithm == self.algorithm
return {
"algorithm": algorithm,
"work_factor": int(work_factor),
"salt": salt,
"block_size": int(block_size),
"parallelism": int(parallelism),
"hash": hash_,
}
def verify(self, password, encoded):
decoded = self.decode(encoded)
encoded_2 = self.encode(
password,
decoded["salt"],
decoded["work_factor"],
decoded["block_size"],
decoded["parallelism"],
)
return constant_time_compare(encoded, encoded_2)
def safe_summary(self, encoded):
decoded = self.decode(encoded)
return {
_("algorithm"): decoded["algorithm"],
_("work factor"): decoded["work_factor"],
_("block size"): decoded["block_size"],
_("parallelism"): decoded["parallelism"],
_("salt"): mask_hash(decoded["salt"]),
_("hash"): mask_hash(decoded["hash"]),
}
def must_update(self, encoded):
decoded = self.decode(encoded)
return (
decoded["work_factor"] != self.work_factor
or decoded["block_size"] != self.block_size
or decoded["parallelism"] != self.parallelism
)
def harden_runtime(self, password, encoded):
# The runtime for Scrypt is too complicated to implement a sensible
# hardening algorithm.
pass
class SHA1PasswordHasher(BasePasswordHasher):
"""
The SHA1 password hashing algorithm (not recommended)
"""
algorithm = "sha1"
def encode(self, password, salt):
self._check_encode_args(password, salt)
assert password is not None
assert salt and '$' not in salt
hash = hashlib.sha1((salt + password).encode()).hexdigest()
return "%s$%s$%s" % (self.algorithm, salt, hash)
def decode(self, encoded):
algorithm, salt, hash = encoded.split("$", 2)
algorithm, salt, hash = encoded.split('$', 2)
assert algorithm == self.algorithm
return {
"algorithm": algorithm,
"hash": hash,
"salt": salt,
'algorithm': algorithm,
'hash': hash,
'salt': salt,
}
def verify(self, password, encoded):
decoded = self.decode(encoded)
encoded_2 = self.encode(password, decoded["salt"])
encoded_2 = self.encode(password, decoded['salt'])
return constant_time_compare(encoded, encoded_2)
def safe_summary(self, encoded):
decoded = self.decode(encoded)
return {
_("algorithm"): decoded["algorithm"],
_("salt"): mask_hash(decoded["salt"], show=2),
_("hash"): mask_hash(decoded["hash"]),
_('algorithm'): decoded['algorithm'],
_('salt'): mask_hash(decoded['salt'], show=2),
_('hash'): mask_hash(decoded['hash']),
}
def must_update(self, encoded):
decoded = self.decode(encoded)
return must_update_salt(decoded["salt"], self.salt_entropy)
return must_update_salt(decoded['salt'], self.salt_entropy)
def harden_runtime(self, password, encoded):
pass
@@ -668,39 +558,39 @@ class MD5PasswordHasher(BasePasswordHasher):
"""
The Salted MD5 password hashing algorithm (not recommended)
"""
algorithm = "md5"
def encode(self, password, salt):
self._check_encode_args(password, salt)
assert password is not None
assert salt and '$' not in salt
hash = hashlib.md5((salt + password).encode()).hexdigest()
return "%s$%s$%s" % (self.algorithm, salt, hash)
def decode(self, encoded):
algorithm, salt, hash = encoded.split("$", 2)
algorithm, salt, hash = encoded.split('$', 2)
assert algorithm == self.algorithm
return {
"algorithm": algorithm,
"hash": hash,
"salt": salt,
'algorithm': algorithm,
'hash': hash,
'salt': salt,
}
def verify(self, password, encoded):
decoded = self.decode(encoded)
encoded_2 = self.encode(password, decoded["salt"])
encoded_2 = self.encode(password, decoded['salt'])
return constant_time_compare(encoded, encoded_2)
def safe_summary(self, encoded):
decoded = self.decode(encoded)
return {
_("algorithm"): decoded["algorithm"],
_("salt"): mask_hash(decoded["salt"], show=2),
_("hash"): mask_hash(decoded["hash"]),
_('algorithm'): decoded['algorithm'],
_('salt'): mask_hash(decoded['salt'], show=2),
_('hash'): mask_hash(decoded['hash']),
}
def must_update(self, encoded):
decoded = self.decode(encoded)
return must_update_salt(decoded["salt"], self.salt_entropy)
return must_update_salt(decoded['salt'], self.salt_entropy)
def harden_runtime(self, password, encoded):
pass
@@ -715,35 +605,33 @@ class UnsaltedSHA1PasswordHasher(BasePasswordHasher):
hashes. Some older Django installs still have these values lingering
around so we need to handle and upgrade them properly.
"""
algorithm = "unsalted_sha1"
def salt(self):
return ""
return ''
def encode(self, password, salt):
if salt != "":
raise ValueError("salt must be empty.")
assert salt == ''
hash = hashlib.sha1(password.encode()).hexdigest()
return "sha1$$%s" % hash
return 'sha1$$%s' % hash
def decode(self, encoded):
assert encoded.startswith("sha1$$")
assert encoded.startswith('sha1$$')
return {
"algorithm": self.algorithm,
"hash": encoded[6:],
"salt": None,
'algorithm': self.algorithm,
'hash': encoded[6:],
'salt': None,
}
def verify(self, password, encoded):
encoded_2 = self.encode(password, "")
encoded_2 = self.encode(password, '')
return constant_time_compare(encoded, encoded_2)
def safe_summary(self, encoded):
decoded = self.decode(encoded)
return {
_("algorithm"): decoded["algorithm"],
_("hash"): mask_hash(decoded["hash"]),
_('algorithm'): decoded['algorithm'],
_('hash'): mask_hash(decoded['hash']),
}
def harden_runtime(self, password, encoded):
@@ -761,35 +649,33 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher):
these values lingering around so we need to handle and upgrade them
properly.
"""
algorithm = "unsalted_md5"
def salt(self):
return ""
return ''
def encode(self, password, salt):
if salt != "":
raise ValueError("salt must be empty.")
assert salt == ''
return hashlib.md5(password.encode()).hexdigest()
def decode(self, encoded):
return {
"algorithm": self.algorithm,
"hash": encoded,
"salt": None,
'algorithm': self.algorithm,
'hash': encoded,
'salt': None,
}
def verify(self, password, encoded):
if len(encoded) == 37 and encoded.startswith("md5$$"):
if len(encoded) == 37 and encoded.startswith('md5$$'):
encoded = encoded[5:]
encoded_2 = self.encode(password, "")
encoded_2 = self.encode(password, '')
return constant_time_compare(encoded, encoded_2)
def safe_summary(self, encoded):
decoded = self.decode(encoded)
return {
_("algorithm"): decoded["algorithm"],
_("hash"): mask_hash(decoded["hash"], show=3),
_('algorithm'): decoded['algorithm'],
_('hash'): mask_hash(decoded['hash'], show=3),
}
def harden_runtime(self, password, encoded):
@@ -802,7 +688,6 @@ class CryptPasswordHasher(BasePasswordHasher):
The crypt module is not supported on all platforms.
"""
algorithm = "crypt"
library = "crypt"
@@ -811,35 +696,33 @@ class CryptPasswordHasher(BasePasswordHasher):
def encode(self, password, salt):
crypt = self._load_library()
if len(salt) != 2:
raise ValueError("salt must be of length 2.")
assert len(salt) == 2
hash = crypt.crypt(password, salt)
if hash is None: # A platform like OpenBSD with a dummy crypt module.
raise TypeError("hash must be provided.")
assert hash is not None # A platform like OpenBSD with a dummy crypt module.
# we don't need to store the salt, but Django used to do this
return "%s$%s$%s" % (self.algorithm, "", hash)
return '%s$%s$%s' % (self.algorithm, '', hash)
def decode(self, encoded):
algorithm, salt, hash = encoded.split("$", 2)
algorithm, salt, hash = encoded.split('$', 2)
assert algorithm == self.algorithm
return {
"algorithm": algorithm,
"hash": hash,
"salt": salt,
'algorithm': algorithm,
'hash': hash,
'salt': salt,
}
def verify(self, password, encoded):
crypt = self._load_library()
decoded = self.decode(encoded)
data = crypt.crypt(password, decoded["hash"])
return constant_time_compare(decoded["hash"], data)
data = crypt.crypt(password, decoded['hash'])
return constant_time_compare(decoded['hash'], data)
def safe_summary(self, encoded):
decoded = self.decode(encoded)
return {
_("algorithm"): decoded["algorithm"],
_("salt"): decoded["salt"],
_("hash"): mask_hash(decoded["hash"], show=3),
_('algorithm'): decoded['algorithm'],
_('salt'): decoded['salt'],
_('hash'): mask_hash(decoded['hash'], show=3),
}
def harden_runtime(self, password, encoded):
@@ -2,7 +2,7 @@
#
# Translators:
# Ahmad Khayyat <akhayyat@gmail.com>, 2013,2021
# Bashar Al-Abdulhadi, 2015-2016,2021
# Bashar Al-Abdulhadi, 2015-2016
# Bashar Al-Abdulhadi, 2014
# Eyad Toma <d.eyad.t@gmail.com>, 2013
# Jannis Leidel <jannis@leidel.info>, 2011
@@ -11,9 +11,9 @@ msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-10-15 21:38+0000\n"
"Last-Translator: Bashar Al-Abdulhadi\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2021-01-03 20:30+0000\n"
"Last-Translator: Ahmad Khayyat <akhayyat@gmail.com>\n"
"Language-Team: Arabic (http://www.transifex.com/django/django/language/ar/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -137,9 +137,6 @@ msgstr "عامل العمل"
msgid "checksum"
msgstr "تدقيق المجموع"
msgid "block size"
msgstr "مقاس الكتله (البلوك)"
msgid "name"
msgstr "الاسم"
@@ -2,13 +2,13 @@
#
# Translators:
# Viktar Palstsiuk <vipals@gmail.com>, 2015
# znotdead <zhirafchik@gmail.com>, 2016-2017,2019,2021
# znotdead <zhirafchik@gmail.com>, 2016-2017,2019
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-09-22 16:48+0000\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2019-10-16 18:29+0000\n"
"Last-Translator: znotdead <zhirafchik@gmail.com>\n"
"Language-Team: Belarusian (http://www.transifex.com/django/django/language/"
"be/)\n"
@@ -137,9 +137,6 @@ msgstr "множнік працы"
msgid "checksum"
msgstr "кантрольная сума"
msgid "block size"
msgstr "памер блока"
msgid "name"
msgstr "назва"
@@ -1,21 +1,20 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# arneatec <arneatec@gmail.com>, 2022
# Boris Chervenkov <office@sentido.bg>, 2012
# Georgi Kostadinov <grgkostadinov@gmail.com>, 2012
# Jannis Leidel <jannis@leidel.info>, 2011
# Lyuboslav Petrov <petrov.lyuboslav@gmail.com>, 2014
# Todor Lubenov <tlubenov@gmail.com>, 2015
# Todor Lubenov <tgl.sysdev@gmail.com>, 2015
# Venelin Stoykov <vkstoykov@gmail.com>, 2015-2016
# vestimir <vestimir@gmail.com>, 2014
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2022-01-14 10:14+0000\n"
"Last-Translator: arneatec <arneatec@gmail.com>\n"
"POT-Creation-Date: 2017-09-24 13:46+0200\n"
"PO-Revision-Date: 2017-09-24 14:24+0000\n"
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
"Language-Team: Bulgarian (http://www.transifex.com/django/django/language/"
"bg/)\n"
"MIME-Version: 1.0\n"
@@ -54,12 +53,12 @@ msgid "last login"
msgstr "последно вписване"
msgid "No password set."
msgstr "Не е зададена парола."
msgstr "Не е запазена парола."
msgid "Invalid password format or unknown hashing algorithm."
msgstr "Невалиден формат за парола или неизвестен алгоритъм за хеширане."
msgid "The two password fields didnt match."
msgid "The two password fields didn't match."
msgstr "Двете полета за паролата не съвпадат. "
msgid "Password"
@@ -72,12 +71,9 @@ msgid "Enter the same password as before, for verification."
msgstr "Въведете същата парола като преди, за да потвърдите."
msgid ""
"Raw passwords are not stored, so there is no way to see this users "
"Raw passwords are not stored, so there is no way to see this user's "
"password, but you can change the password using <a href=\"{}\">this form</a>."
msgstr ""
"Паролите не се съхраняват в чист вид, така че е невъзможно да видите "
"паролата на този потребител, но можете да промените паролата чрез <a href="
"\"{}\">този формуляр</a>."
#, python-format
msgid ""
@@ -85,13 +81,13 @@ msgid ""
"be case-sensitive."
msgstr ""
"Моля, въведете правилните %(username)s и парола. Имайте предвид, че и двете "
"полета могат да бъдат с малки или главни букви."
"полета могат да бъдат малки или главни букви."
msgid "This account is inactive."
msgstr "Този профил е неактивен."
msgid "Email"
msgstr "Имейл"
msgstr "Email"
msgid "New password"
msgstr "Нова парола"
@@ -115,25 +111,25 @@ msgid "iterations"
msgstr "повторения"
msgid "salt"
msgstr "salt"
msgstr "'salt'"
msgid "hash"
msgstr "хеш"
msgid "variety"
msgstr "разнообразие"
msgstr ""
msgid "version"
msgstr "версия"
msgid "memory cost"
msgstr "разход памет"
msgstr ""
msgid "time cost"
msgstr "разход време"
msgstr ""
msgid "parallelism"
msgstr "паралелизъм"
msgstr ""
msgid "work factor"
msgstr "работен фактор"
@@ -141,9 +137,6 @@ msgstr "работен фактор"
msgid "checksum"
msgstr "чексума"
msgid "block size"
msgstr "размер на блока"
msgid "name"
msgstr "име"
@@ -219,8 +212,8 @@ msgid ""
"Designates whether this user should be treated as active. Unselect this "
"instead of deleting accounts."
msgstr ""
"Указва дали този потребител трябва да се третира като активен. Премахнете "
"тази отметката, вместо да изтривате профили."
"Указва дали този потребител трябва да се третира като активен. Премахнете на "
"избора на това, вместо да триете профила."
msgid "date joined"
msgstr "дата на регистриране"
@@ -253,24 +246,24 @@ msgstr[1] "Вашата парола трябва да съдържа поне %
msgid "The password is too similar to the %(verbose_name)s."
msgstr "Паролата е много подобна на %(verbose_name)s."
msgid "Your password cant be too similar to your other personal information."
msgid "Your password can't be too similar to your other personal information."
msgstr "Вашата парола не може да прилича на останалата Ви лична информация."
msgid "This password is too common."
msgstr "Тази парола е често срещана."
msgid "Your password cant be a commonly used password."
msgid "Your password can't be a commonly used password."
msgstr "Вашата парола не може да бъде често срещана."
msgid "This password is entirely numeric."
msgstr "Тази парола е изцяло от цифри."
msgid "Your password cant be entirely numeric."
msgid "Your password can't be entirely numeric."
msgstr "Вашата парола не може да бъде само от цифри."
#, python-format
msgid "Password reset on %(site_name)s"
msgstr "Промяна на парола за %(site_name)s"
msgstr "Променена парола на %(site_name)s"
msgid ""
"Enter a valid username. This value may contain only English letters, "
@@ -293,7 +286,7 @@ msgid "Password reset"
msgstr "Забравена парола"
msgid "Password reset sent"
msgstr "Нулиране на паролата е изпратено"
msgstr "Нулиране на паролата изпратено"
msgid "Enter new password"
msgstr "Въведете нова парола"
@@ -1,15 +1,14 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Arza Grbic <arza.grbic@gmail.com>, 2021
# Jannis Leidel <jannis@leidel.info>, 2011
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-09-27 17:56+0000\n"
"Last-Translator: Arza Grbic <arza.grbic@gmail.com>\n"
"POT-Creation-Date: 2017-09-24 13:46+0200\n"
"PO-Revision-Date: 2017-09-24 14:24+0000\n"
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
"Language-Team: Bosnian (http://www.transifex.com/django/django/language/"
"bs/)\n"
"MIME-Version: 1.0\n"
@@ -30,7 +29,7 @@ msgstr "Važni datumi"
#, python-format
msgid "%(name)s object with primary key %(key)r does not exist."
msgstr "%(name)s objekat sa primarnim ključem %(key)r ne postoji."
msgstr ""
msgid "Password changed successfully."
msgstr "Lozinka uspješno izmjenjena."
@@ -49,14 +48,14 @@ msgid "last login"
msgstr "posljednja prijava"
msgid "No password set."
msgstr "Lozinka nije postavljena."
msgstr ""
msgid "Invalid password format or unknown hashing algorithm."
msgstr "Neispravan format lozinke ili nepoznat hashing algoritam."
msgid "The two password fields didnt match."
msgstr ""
msgid "The two password fields didn't match."
msgstr "Dva polja za lozinku se nisu poklopila."
msgid "Password"
msgstr "Lozinka"
@@ -67,7 +66,7 @@ msgid "Enter the same password as before, for verification."
msgstr ""
msgid ""
"Raw passwords are not stored, so there is no way to see this users "
"Raw passwords are not stored, so there is no way to see this user's "
"password, but you can change the password using <a href=\"{}\">this form</a>."
msgstr ""
@@ -131,9 +130,6 @@ msgstr ""
msgid "checksum"
msgstr ""
msgid "block size"
msgstr ""
msgid "name"
msgstr "ime"
@@ -242,19 +238,19 @@ msgstr[2] ""
msgid "The password is too similar to the %(verbose_name)s."
msgstr ""
msgid "Your password cant be too similar to your other personal information."
msgid "Your password can't be too similar to your other personal information."
msgstr ""
msgid "This password is too common."
msgstr ""
msgid "Your password cant be a commonly used password."
msgid "Your password can't be a commonly used password."
msgstr ""
msgid "This password is entirely numeric."
msgstr ""
msgid "Your password cant be entirely numeric."
msgid "Your password can't be entirely numeric."
msgstr ""
#, python-format
@@ -1,7 +1,7 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Antoni Aloy <aaloy@apsl.net>, 2015,2017,2021
# Antoni Aloy <aaloy@apsl.net>, 2015,2017
# Carles Barrobés <carles@barrobes.com>, 2011-2012,2014-2015
# Gil Obradors Via <gil.obradors@gmail.com>, 2019
# Gil Obradors Via <gil.obradors@gmail.com>, 2019-2020
@@ -13,9 +13,9 @@ msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-10-27 08:48+0000\n"
"Last-Translator: Antoni Aloy <aaloy@apsl.net>\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2021-04-08 22:44+0000\n"
"Last-Translator: Marc Compte <marc@compte.cat>\n"
"Language-Team: Catalan (http://www.transifex.com/django/django/language/"
"ca/)\n"
"MIME-Version: 1.0\n"
@@ -142,9 +142,6 @@ msgstr "factor de treball"
msgid "checksum"
msgstr "suma de comprovació"
msgid "block size"
msgstr "tamany de bloc"
msgid "name"
msgstr "nom"
@@ -5,13 +5,13 @@
# Jannis Leidel <jannis@leidel.info>, 2011
# Tomáš Ehrlich <tomas.ehrlich@gmail.com>, 2015
# Vláďa Macek <macek@sandbox.cz>, 2013-2014
# Vláďa Macek <macek@sandbox.cz>, 2015-2017,2019,2021-2022
# Vláďa Macek <macek@sandbox.cz>, 2015-2017,2019,2021
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2022-01-04 18:49+0000\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2021-03-18 23:35+0000\n"
"Last-Translator: Vláďa Macek <macek@sandbox.cz>\n"
"Language-Team: Czech (http://www.transifex.com/django/django/language/cs/)\n"
"MIME-Version: 1.0\n"
@@ -137,9 +137,6 @@ msgstr "faktor práce"
msgid "checksum"
msgstr "kontrolní součet"
msgid "block size"
msgstr "velikost bloku"
msgid "name"
msgstr "název"
@@ -2,19 +2,18 @@
#
# Translators:
# Christian Joergensen <christian@gmta.info>, 2012
# Erik Ramsgaard Wognsen <r4mses@gmail.com>, 2021
# Erik Ramsgaard Wognsen <r4mses@gmail.com>, 2013-2017,2019
# Erik Wognsen <r4mses@gmail.com>, 2013-2017,2019
# Jannis Leidel <jannis@leidel.info>, 2011
# 85794379431c3e0f5c85c0e72a78d45b_658ddd9, 2013
# Stevenn, 2013
# tiktuk <tiktuk@gmail.com>, 2018
# valberg <valberg@orn.li>, 2015
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-09-23 20:27+0000\n"
"Last-Translator: Erik Ramsgaard Wognsen <r4mses@gmail.com>\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2019-09-17 18:07+0000\n"
"Last-Translator: Erik Wognsen <r4mses@gmail.com>\n"
"Language-Team: Danish (http://www.transifex.com/django/django/language/da/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -140,9 +139,6 @@ msgstr "work factor"
msgid "checksum"
msgstr "tjeksum"
msgid "block size"
msgstr "blokstørrelse"
msgid "name"
msgstr "navn"
@@ -3,7 +3,6 @@
# Translators:
# André Hagenbruch, 2011
# Florian Apolloner <florian@apolloner.eu>, 2012
# Florian Apolloner <florian@apolloner.eu>, 2021
# Jannis Vajen, 2013
# Jannis Leidel <jannis@leidel.info>, 2013-2017,2020
# Jannis Vajen, 2016
@@ -13,9 +12,9 @@ msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-11-28 17:17+0000\n"
"Last-Translator: Raphael Michel <mail@raphaelmichel.de>\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2020-01-17 22:44+0000\n"
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
"Language-Team: German (http://www.transifex.com/django/django/language/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -140,9 +139,6 @@ msgstr "Arbeitsfaktor"
msgid "checksum"
msgstr "Prüfsumme"
msgid "block size"
msgstr "Blockgröße"
msgid "name"
msgstr "Name"
@@ -1,13 +1,13 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Michael Wolf <milupo@sorbzilla.de>, 2016-2017,2020-2021
# Michael Wolf <milupo@sorbzilla.de>, 2016-2017,2020
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-09-28 18:51+0000\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2020-02-25 16:01+0000\n"
"Last-Translator: Michael Wolf <milupo@sorbzilla.de>\n"
"Language-Team: Lower Sorbian (http://www.transifex.com/django/django/"
"language/dsb/)\n"
@@ -136,9 +136,6 @@ msgstr "źěłowy faktor"
msgid "checksum"
msgstr "kontrolna suma"
msgid "block size"
msgstr "blokowa wjelikosć"
msgid "name"
msgstr "mě"
@@ -10,15 +10,14 @@
# Nick Mavrakis <mavrakis.n@gmail.com>, 2018
# Pãnoș <panos.laganakos@gmail.com>, 2014
# Pãnoș <panos.laganakos@gmail.com>, 2016
# Serafeim Papastefanos <spapas@gmail.com>, 2021
# Yorgos Pagles <y.pagles@gmail.com>, 2011
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-09-22 09:22+0000\n"
"Last-Translator: Transifex Bot <>\n"
"POT-Creation-Date: 2017-09-24 13:46+0200\n"
"PO-Revision-Date: 2018-09-22 09:59+0000\n"
"Last-Translator: Nick Mavrakis <mavrakis.n@gmail.com>\n"
"Language-Team: Greek (http://www.transifex.com/django/django/language/el/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -61,8 +60,8 @@ msgstr "Δεν έχει τεθεί συνθηματικό."
msgid "Invalid password format or unknown hashing algorithm."
msgstr "Μη έγκυρη μορφή συνθηματικού ή άγνωστος αλγόριθμος hashing."
msgid "The two password fields didnt match."
msgstr "Τα δύο πεδία κωδικών δεν ταιριάζουν."
msgid "The two password fields didn't match."
msgstr "Τα δύο πεδία συνθηματικού δεν ταιριάζουν."
msgid "Password"
msgstr "Συνθηματικό"
@@ -74,7 +73,7 @@ msgid "Enter the same password as before, for verification."
msgstr "Εισάγετε το ίδιο συνθηματικό όπως πρίν, για επιβεβαίωση."
msgid ""
"Raw passwords are not stored, so there is no way to see this users "
"Raw passwords are not stored, so there is no way to see this user's "
"password, but you can change the password using <a href=\"{}\">this form</a>."
msgstr ""
"Οι ακατέργαστοι κωδικοί δεν αποθηκεύονται, οπότε δεν υπάρχει τρόπος να δείτε "
@@ -143,9 +142,6 @@ msgstr "work factor"
msgid "checksum"
msgstr "checksum"
msgid "block size"
msgstr ""
msgid "name"
msgstr "όνομα"
@@ -261,21 +257,21 @@ msgstr[1] ""
msgid "The password is too similar to the %(verbose_name)s."
msgstr "Το συνθηματικό μοιάζει πολύ με το %(verbose_name)s."
msgid "Your password cant be too similar to your other personal information."
msgid "Your password can't be too similar to your other personal information."
msgstr ""
"Ο κωδικός σας δεν μπορεί να μοιάζει τόσο με τα άλλα προσωπικά σας στοιχεία."
"Το συνθηματικό σας δεν μπορεί να μοιάζει τόσο με άλλα προσωπικά σας στοιχεία."
msgid "This password is too common."
msgstr "Πολύ κοινό συνθηματικό."
msgid "Your password cant be a commonly used password."
msgstr "Ο κωδικός σας δεν μπορεί να είναι τόσο συνηθισμένος."
msgid "Your password can't be a commonly used password."
msgstr "Το συνθηματικό δεν μπορεί να είναι τόσο συνηθισμένο."
msgid "This password is entirely numeric."
msgstr "Αυτό το συνθηματικό αποτελείται μόνο απο αριθμούς."
msgid "Your password cant be entirely numeric."
msgstr "Ο κωδικός σας δε μπορεί να αποτελείται μόνον από αριθμούς."
msgid "Your password can't be entirely numeric."
msgstr "Το συνθηματικό σας δεν μπορεί να ειναι αποκλειστικά αριθμητικό."
#, python-format
msgid "Password reset on %(site_name)s"
@@ -4,7 +4,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
"Last-Translator: Django team\n"
"Language-Team: English <en@li.org>\n"
@@ -40,7 +40,7 @@ msgstr ""
msgid "Change password: %s"
msgstr ""
#: contrib/auth/apps.py:16
#: contrib/auth/apps.py:15
msgid "Authentication and Authorization"
msgstr ""
@@ -52,32 +52,32 @@ msgstr ""
msgid "last login"
msgstr ""
#: contrib/auth/forms.py:41
#: contrib/auth/forms.py:31
msgid "No password set."
msgstr ""
#: contrib/auth/forms.py:46
#: contrib/auth/forms.py:36
msgid "Invalid password format or unknown hashing algorithm."
msgstr ""
#: contrib/auth/forms.py:84 contrib/auth/forms.py:325 contrib/auth/forms.py:398
#: contrib/auth/forms.py:78 contrib/auth/forms.py:316 contrib/auth/forms.py:389
msgid "The two password fields didnt match."
msgstr ""
#: contrib/auth/forms.py:87 contrib/auth/forms.py:140 contrib/auth/forms.py:170
#: contrib/auth/forms.py:402
#: contrib/auth/forms.py:81 contrib/auth/forms.py:134 contrib/auth/forms.py:170
#: contrib/auth/forms.py:393
msgid "Password"
msgstr ""
#: contrib/auth/forms.py:93
#: contrib/auth/forms.py:87
msgid "Password confirmation"
msgstr ""
#: contrib/auth/forms.py:96 contrib/auth/forms.py:411
#: contrib/auth/forms.py:90 contrib/auth/forms.py:402
msgid "Enter the same password as before, for verification."
msgstr ""
#: contrib/auth/forms.py:142
#: contrib/auth/forms.py:136
msgid ""
"Raw passwords are not stored, so there is no way to see this users "
"password, but you can change the password using <a href=\"{}\">this form</a>."
@@ -94,191 +94,185 @@ msgstr ""
msgid "This account is inactive."
msgstr ""
#: contrib/auth/forms.py:243
#: contrib/auth/forms.py:241
msgid "Email"
msgstr ""
#: contrib/auth/forms.py:328
#: contrib/auth/forms.py:319
msgid "New password"
msgstr ""
#: contrib/auth/forms.py:334
#: contrib/auth/forms.py:325
msgid "New password confirmation"
msgstr ""
#: contrib/auth/forms.py:370
#: contrib/auth/forms.py:361
msgid "Your old password was entered incorrectly. Please enter it again."
msgstr ""
#: contrib/auth/forms.py:373
#: contrib/auth/forms.py:364
msgid "Old password"
msgstr ""
#: contrib/auth/forms.py:408
#: contrib/auth/forms.py:399
msgid "Password (again)"
msgstr ""
#: contrib/auth/hashers.py:302 contrib/auth/hashers.py:393
#: contrib/auth/hashers.py:482 contrib/auth/hashers.py:573
#: contrib/auth/hashers.py:623 contrib/auth/hashers.py:664
#: contrib/auth/hashers.py:712 contrib/auth/hashers.py:757
#: contrib/auth/hashers.py:805
#: contrib/auth/hashers.py:259 contrib/auth/hashers.py:333
#: contrib/auth/hashers.py:429 contrib/auth/hashers.py:489
#: contrib/auth/hashers.py:520 contrib/auth/hashers.py:556
#: contrib/auth/hashers.py:592 contrib/auth/hashers.py:630
msgid "algorithm"
msgstr ""
#: contrib/auth/hashers.py:303
#: contrib/auth/hashers.py:260
msgid "iterations"
msgstr ""
#: contrib/auth/hashers.py:304 contrib/auth/hashers.py:399
#: contrib/auth/hashers.py:484 contrib/auth/hashers.py:577
#: contrib/auth/hashers.py:624 contrib/auth/hashers.py:665
#: contrib/auth/hashers.py:806
#: contrib/auth/hashers.py:261 contrib/auth/hashers.py:339
#: contrib/auth/hashers.py:431 contrib/auth/hashers.py:490
#: contrib/auth/hashers.py:521 contrib/auth/hashers.py:631
msgid "salt"
msgstr ""
#: contrib/auth/hashers.py:305 contrib/auth/hashers.py:400
#: contrib/auth/hashers.py:578 contrib/auth/hashers.py:625
#: contrib/auth/hashers.py:666 contrib/auth/hashers.py:713
#: contrib/auth/hashers.py:758 contrib/auth/hashers.py:807
#: contrib/auth/hashers.py:262 contrib/auth/hashers.py:340
#: contrib/auth/hashers.py:491 contrib/auth/hashers.py:522
#: contrib/auth/hashers.py:557 contrib/auth/hashers.py:593
#: contrib/auth/hashers.py:632
msgid "hash"
msgstr ""
#: contrib/auth/hashers.py:394
#: contrib/auth/hashers.py:334
msgid "variety"
msgstr ""
#: contrib/auth/hashers.py:395
#: contrib/auth/hashers.py:335
msgid "version"
msgstr ""
#: contrib/auth/hashers.py:396
#: contrib/auth/hashers.py:336
msgid "memory cost"
msgstr ""
#: contrib/auth/hashers.py:397
#: contrib/auth/hashers.py:337
msgid "time cost"
msgstr ""
#: contrib/auth/hashers.py:398 contrib/auth/hashers.py:576
#: contrib/auth/hashers.py:338
msgid "parallelism"
msgstr ""
#: contrib/auth/hashers.py:483 contrib/auth/hashers.py:574
#: contrib/auth/hashers.py:430
msgid "work factor"
msgstr ""
#: contrib/auth/hashers.py:485
#: contrib/auth/hashers.py:432
msgid "checksum"
msgstr ""
#: contrib/auth/hashers.py:575
msgid "block size"
msgstr ""
#: contrib/auth/models.py:58 contrib/auth/models.py:109
#: contrib/auth/models.py:56 contrib/auth/models.py:108
msgid "name"
msgstr ""
#: contrib/auth/models.py:62
#: contrib/auth/models.py:60
msgid "content type"
msgstr ""
#: contrib/auth/models.py:64
#: contrib/auth/models.py:62
msgid "codename"
msgstr ""
#: contrib/auth/models.py:69
#: contrib/auth/models.py:67
msgid "permission"
msgstr ""
#: contrib/auth/models.py:70 contrib/auth/models.py:112
#: contrib/auth/models.py:68 contrib/auth/models.py:111
msgid "permissions"
msgstr ""
#: contrib/auth/models.py:119
#: contrib/auth/models.py:118
msgid "group"
msgstr ""
#: contrib/auth/models.py:120 contrib/auth/models.py:247
#: contrib/auth/models.py:119 contrib/auth/models.py:242
msgid "groups"
msgstr ""
#: contrib/auth/models.py:238
#: contrib/auth/models.py:233
msgid "superuser status"
msgstr ""
#: contrib/auth/models.py:241
#: contrib/auth/models.py:236
msgid ""
"Designates that this user has all permissions without explicitly assigning "
"them."
msgstr ""
#: contrib/auth/models.py:250
#: contrib/auth/models.py:245
msgid ""
"The groups this user belongs to. A user will get all permissions granted to "
"each of their groups."
msgstr ""
#: contrib/auth/models.py:258
#: contrib/auth/models.py:253
msgid "user permissions"
msgstr ""
#: contrib/auth/models.py:260
#: contrib/auth/models.py:255
msgid "Specific permissions for this user."
msgstr ""
#: contrib/auth/models.py:331
#: contrib/auth/models.py:326
msgid "username"
msgstr ""
#: contrib/auth/models.py:334
#: contrib/auth/models.py:329
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
msgstr ""
#: contrib/auth/models.py:337
#: contrib/auth/models.py:332
msgid "A user with that username already exists."
msgstr ""
#: contrib/auth/models.py:340
#: contrib/auth/models.py:335
msgid "first name"
msgstr ""
#: contrib/auth/models.py:341
#: contrib/auth/models.py:336
msgid "last name"
msgstr ""
#: contrib/auth/models.py:342
#: contrib/auth/models.py:337
msgid "email address"
msgstr ""
#: contrib/auth/models.py:344
#: contrib/auth/models.py:339
msgid "staff status"
msgstr ""
#: contrib/auth/models.py:346
#: contrib/auth/models.py:341
msgid "Designates whether the user can log into this admin site."
msgstr ""
#: contrib/auth/models.py:349
#: contrib/auth/models.py:344
msgid "active"
msgstr ""
#: contrib/auth/models.py:352
#: contrib/auth/models.py:347
msgid ""
"Designates whether this user should be treated as active. Unselect this "
"instead of deleting accounts."
msgstr ""
#: contrib/auth/models.py:356
#: contrib/auth/models.py:351
msgid "date joined"
msgstr ""
#: contrib/auth/models.py:365
#: contrib/auth/models.py:360
msgid "user"
msgstr ""
#: contrib/auth/models.py:366
#: contrib/auth/models.py:361
msgid "users"
msgstr ""
@@ -309,19 +303,19 @@ msgstr ""
msgid "Your password cant be too similar to your other personal information."
msgstr ""
#: contrib/auth/password_validation.py:188
#: contrib/auth/password_validation.py:183
msgid "This password is too common."
msgstr ""
#: contrib/auth/password_validation.py:193
#: contrib/auth/password_validation.py:188
msgid "Your password cant be a commonly used password."
msgstr ""
#: contrib/auth/password_validation.py:203
#: contrib/auth/password_validation.py:198
msgid "This password is entirely numeric."
msgstr ""
#: contrib/auth/password_validation.py:208
#: contrib/auth/password_validation.py:203
msgid "Your password cant be entirely numeric."
msgstr ""
@@ -342,34 +336,34 @@ msgid ""
"@/./+/-/_ characters."
msgstr ""
#: contrib/auth/views.py:164
#: contrib/auth/views.py:160
msgid "Logged out"
msgstr ""
#: contrib/auth/views.py:221
#: contrib/auth/views.py:217
msgid "Password reset"
msgstr ""
#: contrib/auth/views.py:248
#: contrib/auth/views.py:244
msgid "Password reset sent"
msgstr ""
#: contrib/auth/views.py:258
#: contrib/auth/views.py:254
msgid "Enter new password"
msgstr ""
#: contrib/auth/views.py:321
#: contrib/auth/views.py:314
msgid "Password reset unsuccessful"
msgstr ""
#: contrib/auth/views.py:329
#: contrib/auth/views.py:322
msgid "Password reset complete"
msgstr ""
#: contrib/auth/views.py:341
#: contrib/auth/views.py:334
msgid "Password change"
msgstr ""
#: contrib/auth/views.py:364
#: contrib/auth/views.py:357
msgid "Password change successful"
msgstr ""
@@ -1,15 +1,14 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Tom Fifield <tom@tomfifield.net>, 2014
# Tom Fifield <tom@tomfifield.net>, 2021
# Tom Fifield <tom@openstack.org>, 2014
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-09-22 09:22+0000\n"
"Last-Translator: Transifex Bot <>\n"
"POT-Creation-Date: 2017-09-24 13:46+0200\n"
"PO-Revision-Date: 2017-09-24 14:24+0000\n"
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
"Language-Team: English (Australia) (http://www.transifex.com/django/django/"
"language/en_AU/)\n"
"MIME-Version: 1.0\n"
@@ -29,7 +28,7 @@ msgstr "Important dates"
#, python-format
msgid "%(name)s object with primary key %(key)r does not exist."
msgstr "%(name)s object with primary key %(key)r does not exist."
msgstr ""
msgid "Password changed successfully."
msgstr "Password changed successfully."
@@ -39,7 +38,7 @@ msgid "Change password: %s"
msgstr "Change password: %s"
msgid "Authentication and Authorization"
msgstr "Authentication and Authorisation"
msgstr ""
msgid "password"
msgstr "password"
@@ -53,8 +52,8 @@ msgstr "No password set."
msgid "Invalid password format or unknown hashing algorithm."
msgstr "Invalid password format or unknown hashing algorithm."
msgid "The two password fields didnt match."
msgstr "The two password fields didnt match."
msgid "The two password fields didn't match."
msgstr "The two password fields didn't match."
msgid "Password"
msgstr "Password"
@@ -63,14 +62,12 @@ msgid "Password confirmation"
msgstr "Password confirmation"
msgid "Enter the same password as before, for verification."
msgstr "Enter the same password as before, for verification."
msgstr ""
msgid ""
"Raw passwords are not stored, so there is no way to see this users "
"Raw passwords are not stored, so there is no way to see this user's "
"password, but you can change the password using <a href=\"{}\">this form</a>."
msgstr ""
"Raw passwords are not stored, so there is no way to see this users "
"password, but you can change the password using <a href=\"{}\">this form</a>."
#, python-format
msgid ""
@@ -114,19 +111,19 @@ msgid "hash"
msgstr "hash"
msgid "variety"
msgstr "variety"
msgstr ""
msgid "version"
msgstr "version"
msgstr ""
msgid "memory cost"
msgstr "memory cost"
msgstr ""
msgid "time cost"
msgstr "time cost"
msgstr ""
msgid "parallelism"
msgstr "parallelism"
msgstr ""
msgid "work factor"
msgstr "work factor"
@@ -134,14 +131,11 @@ msgstr "work factor"
msgid "checksum"
msgstr "checksum"
msgid "block size"
msgstr ""
msgid "name"
msgstr "name"
msgid "content type"
msgstr "content type"
msgstr ""
msgid "codename"
msgstr "codename"
@@ -172,20 +166,18 @@ msgid ""
"The groups this user belongs to. A user will get all permissions granted to "
"each of their groups."
msgstr ""
"The groups this user belongs to. A user will get all permissions granted to "
"each of their groups."
msgid "user permissions"
msgstr "user permissions"
msgid "Specific permissions for this user."
msgstr "Specific permissions for this user."
msgstr ""
msgid "username"
msgstr "username"
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
msgstr "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
msgstr ""
msgid "A user with that username already exists."
msgstr "A user with that username already exists."
@@ -232,36 +224,32 @@ msgid_plural ""
"This password is too short. It must contain at least %(min_length)d "
"characters."
msgstr[0] ""
"This password is too short. It must contain at least %(min_length)d "
"character."
msgstr[1] ""
"This password is too short. It must contain at least %(min_length)d "
"characters."
#, python-format
msgid "Your password must contain at least %(min_length)d character."
msgid_plural "Your password must contain at least %(min_length)d characters."
msgstr[0] "Your password must contain at least %(min_length)d character."
msgstr[1] "Your password must contain at least %(min_length)d characters."
msgstr[0] ""
msgstr[1] ""
#, python-format
msgid "The password is too similar to the %(verbose_name)s."
msgstr "The password is too similar to the %(verbose_name)s."
msgstr ""
msgid "Your password cant be too similar to your other personal information."
msgstr "Your password cant be too similar to your other personal information."
msgid "Your password can't be too similar to your other personal information."
msgstr ""
msgid "This password is too common."
msgstr "This password is too common."
msgstr ""
msgid "Your password cant be a commonly used password."
msgstr "Your password cant be a commonly used password."
msgid "Your password can't be a commonly used password."
msgstr ""
msgid "This password is entirely numeric."
msgstr "This password is entirely numeric."
msgstr ""
msgid "Your password cant be entirely numeric."
msgstr "Your password cant be entirely numeric."
msgid "Your password can't be entirely numeric."
msgstr ""
#, python-format
msgid "Password reset on %(site_name)s"
@@ -271,36 +259,32 @@ msgid ""
"Enter a valid username. This value may contain only English letters, "
"numbers, and @/./+/-/_ characters."
msgstr ""
"Enter a valid username. This value may contain only English letters, "
"numbers, and @/./+/-/_ characters."
msgid ""
"Enter a valid username. This value may contain only letters, numbers, and "
"@/./+/-/_ characters."
msgstr ""
"Enter a valid username. This value may contain only letters, numbers, and "
"@/./+/-/_ characters."
msgid "Logged out"
msgstr "Logged out"
msgid "Password reset"
msgstr "Password reset"
msgstr ""
msgid "Password reset sent"
msgstr "Password reset sent"
msgstr ""
msgid "Enter new password"
msgstr "Enter new password"
msgstr ""
msgid "Password reset unsuccessful"
msgstr "Password reset unsuccessful"
msgstr ""
msgid "Password reset complete"
msgstr "Password reset complete"
msgstr ""
msgid "Password change"
msgstr "Password change"
msgstr ""
msgid "Password change successful"
msgstr "Password change successful"
msgstr ""
@@ -3,22 +3,22 @@
# Translators:
# albertoalcolea <albertoalcolea@gmail.com>, 2014
# Antoni Aloy <aaloy@apsl.net>, 2012-2013,2015-2017
# e4db27214f7e7544f2022c647b585925_bb0e321, 2015-2016
# e4db27214f7e7544f2022c647b585925_bb0e321, 2020
# Ernesto Avilés, 2015-2016
# Ernesto Avilés, 2020
# Ernesto Rico Schmidt <e.rico.schmidt@gmail.com>, 2017
# guillem <serra.guillem@gmail.com>, 2012
# Igor Támara <igor@tamarapatino.org>, 2015
# Jannis Leidel <jannis@leidel.info>, 2011
# Josue Naaman Nistal Guerra <josuenistal@hotmail.com>, 2014
# Leonardo J. Caballero G. <leonardocaballero@gmail.com>, 2011
# Uriel Medina <urimeba511@gmail.com>, 2020-2021
# Uriel Medina <urimeba511@gmail.com>, 2020
# Veronicabh <vero.blazher@gmail.com>, 2015
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-11-10 03:52+0000\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2020-09-25 17:48+0000\n"
"Last-Translator: Uriel Medina <urimeba511@gmail.com>\n"
"Language-Team: Spanish (http://www.transifex.com/django/django/language/"
"es/)\n"
@@ -146,9 +146,6 @@ msgstr "factor trabajo"
msgid "checksum"
msgstr "suma de verificación"
msgid "block size"
msgstr "tamaño de bloque"
msgid "name"
msgstr "nombre"
@@ -2,13 +2,13 @@
#
# Translators:
# Jannis Leidel <jannis@leidel.info>, 2011
# Ramiro Morales, 2013-2017,2019,2021
# Ramiro Morales, 2013-2017,2019
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-11-19 13:26+0000\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2019-10-01 10:21+0000\n"
"Last-Translator: Ramiro Morales\n"
"Language-Team: Spanish (Argentina) (http://www.transifex.com/django/django/"
"language/es_AR/)\n"
@@ -138,9 +138,6 @@ msgstr "work factor"
msgid "checksum"
msgstr "suma de verificación"
msgid "block size"
msgstr "tamaño de bloque"
msgid "name"
msgstr "nombre"
@@ -4,9 +4,8 @@
# Ahmad Hosseini <ahmadly.com@gmail.com>, 2020
# Ali Nikneshan <ali@nikneshan.com>, 2015
# Eric Hamiter <ehamiter@gmail.com>, 2013
# Farshad Asadpour, 2021
# Jannis Leidel <jannis@leidel.info>, 2011
# cef32bddc4c7e18de7e89af20a3a57ef_18bb97f, 2015
# Kaveh Karimi, 2015
# MJafar Mashhadi <raindigital2007@gmail.com>, 2018
# Pouya Abbassi, 2016
# Reza Mohammadi <reza@teeleh.ir>, 2013-2014
@@ -14,9 +13,9 @@ msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-11-19 17:35+0000\n"
"Last-Translator: Farshad Asadpour\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2020-08-20 15:54+0000\n"
"Last-Translator: Ahmad Hosseini <ahmadly.com@gmail.com>\n"
"Language-Team: Persian (http://www.transifex.com/django/django/language/"
"fa/)\n"
"MIME-Version: 1.0\n"
@@ -141,9 +140,6 @@ msgstr "عامل کار"
msgid "checksum"
msgstr "جمع کنترلی"
msgid "block size"
msgstr "اندازه بلاک"
msgid "name"
msgstr "نام"
@@ -1,7 +1,7 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Aarni Koskela, 2015,2017-2018,2020-2021
# Aarni Koskela, 2015,2017-2018,2020
# Antti Kaihola <antti.15+transifex@kaihola.fi>, 2011
# Jannis Leidel <jannis@leidel.info>, 2011
# Klaus Dahlén <klaus.dahlen@gmail.com>, 2012
@@ -9,8 +9,8 @@ msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-11-22 15:17+0000\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2020-01-21 09:33+0000\n"
"Last-Translator: Aarni Koskela\n"
"Language-Team: Finnish (http://www.transifex.com/django/django/language/"
"fi/)\n"
@@ -137,9 +137,6 @@ msgstr "työmäärä"
msgid "checksum"
msgstr "tarkistussumma"
msgid "block size"
msgstr "lohkokoko"
msgid "name"
msgstr "nimi"
@@ -1,7 +1,7 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Claude Paroz <claude@2xlibre.net>, 2013-2019,2021
# Claude Paroz <claude@2xlibre.net>, 2013-2019
# Claude Paroz <claude@2xlibre.net>, 2013
# Jannis Leidel <jannis@leidel.info>, 2011
# mlorant <maxime.lorant@gmail.com>, 2014
@@ -9,8 +9,8 @@ msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-09-25 09:56+0000\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2019-09-18 15:54+0000\n"
"Last-Translator: Claude Paroz <claude@2xlibre.net>\n"
"Language-Team: French (http://www.transifex.com/django/django/language/fr/)\n"
"MIME-Version: 1.0\n"
@@ -138,9 +138,6 @@ msgstr "facteur travail"
msgid "checksum"
msgstr "somme de contrôle"
msgid "block size"
msgstr "taille de bloc"
msgid "name"
msgstr "nom"
@@ -1,15 +1,15 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# GunChleoc, 2015-2017,2021
# GunChleoc, 2015-2017
# GunChleoc, 2015
# GunChleoc, 2015
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-10-27 12:55+0000\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2019-12-13 12:36+0000\n"
"Last-Translator: GunChleoc\n"
"Language-Team: Gaelic, Scottish (http://www.transifex.com/django/django/"
"language/gd/)\n"
@@ -141,9 +141,6 @@ msgstr "factar obrachaidh"
msgid "checksum"
msgstr "àireamh dhearbhaidh"
msgid "block size"
msgstr "meud nam blocaichean"
msgid "name"
msgstr "ainm"
@@ -1,13 +1,13 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Michael Wolf <milupo@sorbzilla.de>, 2016-2017,2019,2021
# Michael Wolf <milupo@sorbzilla.de>, 2016-2017,2019
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-09-28 18:17+0000\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2019-09-21 19:21+0000\n"
"Last-Translator: Michael Wolf <milupo@sorbzilla.de>\n"
"Language-Team: Upper Sorbian (http://www.transifex.com/django/django/"
"language/hsb/)\n"
@@ -134,9 +134,6 @@ msgstr "dźěłowy faktor"
msgid "checksum"
msgstr "pruwowanska suma"
msgid "block size"
msgstr "blokowa wulkosć"
msgid "name"
msgstr "mjeno"
@@ -1,14 +1,14 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Martijn Dekker <mcdutchie@hotmail.com>, 2012,2021
# Martijn Dekker <mcdutchie@hotmail.com>, 2012
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-09-22 09:22+0000\n"
"Last-Translator: Transifex Bot <>\n"
"POT-Creation-Date: 2017-09-24 13:46+0200\n"
"PO-Revision-Date: 2017-09-24 14:24+0000\n"
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
"Language-Team: Interlingua (http://www.transifex.com/django/django/language/"
"ia/)\n"
"MIME-Version: 1.0\n"
@@ -28,7 +28,7 @@ msgstr "Datas importante"
#, python-format
msgid "%(name)s object with primary key %(key)r does not exist."
msgstr "Le objecto %(name)s con le clave primari %(key)r non existe."
msgstr ""
msgid "Password changed successfully."
msgstr "Le cambio del contrasigno ha succedite."
@@ -38,7 +38,7 @@ msgid "Change password: %s"
msgstr "Cambia contrasigno: %s"
msgid "Authentication and Authorization"
msgstr "Authentication e autorisation"
msgstr ""
msgid "password"
msgstr "contrasigno"
@@ -47,13 +47,12 @@ msgid "last login"
msgstr "ultime session"
msgid "No password set."
msgstr "Nulle contrasigno definite."
msgstr ""
msgid "Invalid password format or unknown hashing algorithm."
msgstr ""
"Le formato del contrasigno es invalide o le algorithmo de hash es incognite."
msgid "The two password fields didnt match."
msgid "The two password fields didn't match."
msgstr "Le duo campos de contrasigno non es identic."
msgid "Password"
@@ -63,29 +62,24 @@ msgid "Password confirmation"
msgstr "Confirma contrasigno"
msgid "Enter the same password as before, for verification."
msgstr "Scribe le mesme contrasigno que antea, pro verification."
msgstr ""
msgid ""
"Raw passwords are not stored, so there is no way to see this users "
"Raw passwords are not stored, so there is no way to see this user's "
"password, but you can change the password using <a href=\"{}\">this form</a>."
msgstr ""
"Le contrasignos non es immagazinate in forma de texto simple, dunque il non "
"es possibile vider le contrasigno de iste usator, ma tu pote cambiar le "
"contrasigno con <a href=\"{}\">iste formulario</a>."
#, python-format
msgid ""
"Please enter a correct %(username)s and password. Note that both fields may "
"be case-sensitive."
msgstr ""
"Per favor entra un %(username)s e contrasigno correcte. Nota que ambe campos "
"pote distinguer inter majusculas e minusculas."
msgid "This account is inactive."
msgstr "Iste conto es inactive."
msgid "Email"
msgstr "E-mail"
msgstr ""
msgid "New password"
msgstr "Nove contrasigno"
@@ -115,19 +109,19 @@ msgid "hash"
msgstr "hash"
msgid "variety"
msgstr "varietate"
msgstr ""
msgid "version"
msgstr "version"
msgstr ""
msgid "memory cost"
msgstr "costo de memoria"
msgstr ""
msgid "time cost"
msgstr "costo de tempore"
msgstr ""
msgid "parallelism"
msgstr "parallelismo"
msgstr ""
msgid "work factor"
msgstr "factor de labor"
@@ -135,14 +129,11 @@ msgstr "factor de labor"
msgid "checksum"
msgstr "summa de controlo"
msgid "block size"
msgstr ""
msgid "name"
msgstr "nomine"
msgid "content type"
msgstr "typo de contento"
msgstr ""
msgid "codename"
msgstr "nomine de codice"
@@ -173,21 +164,18 @@ msgid ""
"The groups this user belongs to. A user will get all permissions granted to "
"each of their groups."
msgstr ""
"Le gruppos al quales iste usator pertine. Un usator recipe tote le "
"permissiones concedite a cata un de su gruppos."
msgid "user permissions"
msgstr "permissiones de usator"
msgid "Specific permissions for this user."
msgstr "Permissiones specific pro iste usator."
msgstr ""
msgid "username"
msgstr "nomine de usator"
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
msgstr ""
"Obligatori. 150 characteres o minus. Litteras, cifras e @/./+/-/_ solmente."
msgid "A user with that username already exists."
msgstr "Un usator con iste nomine de usator jam existe."
@@ -199,7 +187,7 @@ msgid "last name"
msgstr "nomine de familia"
msgid "email address"
msgstr "adresse de e-mail"
msgstr ""
msgid "staff status"
msgstr "stato de personal"
@@ -234,38 +222,32 @@ msgid_plural ""
"This password is too short. It must contain at least %(min_length)d "
"characters."
msgstr[0] ""
"Le contrasigno es troppo curte. Debe continer al minus %(min_length)d "
"character."
msgstr[1] ""
"Le contrasigno es troppo curte. Debe continer al minus %(min_length)d "
"characteres."
#, python-format
msgid "Your password must contain at least %(min_length)d character."
msgid_plural "Your password must contain at least %(min_length)d characters."
msgstr[0] "Le contrasigno debe continer al minus %(min_length)d character."
msgstr[1] "Le contrasigno debe continer al minus %(min_length)d characteres."
msgstr[0] ""
msgstr[1] ""
#, python-format
msgid "The password is too similar to the %(verbose_name)s."
msgstr "Le contrasigno es troppo simile al %(verbose_name)s."
msgid "Your password cant be too similar to your other personal information."
msgstr ""
"Le contrasigno non pote esser troppo similar a tu altere informationes "
"personal."
msgid "Your password can't be too similar to your other personal information."
msgstr ""
msgid "This password is too common."
msgstr "Iste contrasigno es troppo commun."
msgstr ""
msgid "Your password cant be a commonly used password."
msgstr "Le contrasigno non pote esser un contrasigno communmente usate."
msgid "Your password can't be a commonly used password."
msgstr ""
msgid "This password is entirely numeric."
msgstr "Iste contrasigno es toto numeric."
msgstr ""
msgid "Your password cant be entirely numeric."
msgstr "Le contrasigno non pote esser toto numeric."
msgid "Your password can't be entirely numeric."
msgstr ""
#, python-format
msgid "Password reset on %(site_name)s"
@@ -275,36 +257,32 @@ msgid ""
"Enter a valid username. This value may contain only English letters, "
"numbers, and @/./+/-/_ characters."
msgstr ""
"Entra un nomine de usator valide. Pote continer solmente litteras anglese, "
"numeros e le characteres @/./+/-/_."
msgid ""
"Enter a valid username. This value may contain only letters, numbers, and "
"@/./+/-/_ characters."
msgstr ""
"Entra un nomine de usator valide. Pote continer solmente litteras, numeros e "
"le characteres @/./+/-/_."
msgid "Logged out"
msgstr "Session claudite"
msgid "Password reset"
msgstr "Reinitialisation del contrasigno"
msgstr ""
msgid "Password reset sent"
msgstr "Reinitialisation del contrasigno inviate"
msgstr ""
msgid "Enter new password"
msgstr "Scribe nove contrasigno"
msgstr ""
msgid "Password reset unsuccessful"
msgstr "Reinitialisation de contrasigno fallite"
msgstr ""
msgid "Password reset complete"
msgstr "Contrasigno reinitialisate con successo"
msgstr ""
msgid "Password change"
msgstr "Cambio de contrasigno"
msgstr ""
msgid "Password change successful"
msgstr "Contrasigno cambiate con successo"
msgstr ""
@@ -1,7 +1,6 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Davide Targa <davide.targa@gmail.com>, 2021
# Federico Capoano <federico.capoano@teletu.it>, 2011
# Flavio Curella <flavio.curella@gmail.com>, 2013-2014
# Jannis Leidel <jannis@leidel.info>, 2011
@@ -14,9 +13,9 @@ msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-11-12 12:08+0000\n"
"Last-Translator: Davide Targa <davide.targa@gmail.com>\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2019-09-24 08:45+0000\n"
"Last-Translator: palmux <palmux@gmail.com>\n"
"Language-Team: Italian (http://www.transifex.com/django/django/language/"
"it/)\n"
"MIME-Version: 1.0\n"
@@ -143,9 +142,6 @@ msgstr "work factor"
msgid "checksum"
msgstr "checksum"
msgid "block size"
msgstr "dimensione del blocco"
msgid "name"
msgstr "nome"
@@ -5,14 +5,14 @@
# arupakan125 <koh@arupaka.net>, 2020
# Masashi SHIBATA <contact@c-bata.link>, 2017
# Nikita K <hiyori.amatsuki@gmail.com>, 2019
# Shinya Okano <tokibito@gmail.com>, 2013-2016,2021
# Shinya Okano <tokibito@gmail.com>, 2013-2016
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-10-13 11:35+0000\n"
"Last-Translator: Shinya Okano <tokibito@gmail.com>\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2020-01-20 06:25+0000\n"
"Last-Translator: arupakan125 <koh@arupaka.net>\n"
"Language-Team: Japanese (http://www.transifex.com/django/django/language/"
"ja/)\n"
"MIME-Version: 1.0\n"
@@ -138,9 +138,6 @@ msgstr "ワークファクター"
msgid "checksum"
msgstr "チェックサム"
msgid "block size"
msgstr "ブロックサイズ"
msgid "name"
msgstr "名前"
@@ -16,7 +16,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: kn\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"Plural-Forms: nplurals=1; plural=0;\n"
msgid "Personal info"
msgstr "ವೈಯುಕ್ತಿಕ ಮಾಹಿತಿ"
@@ -5,7 +5,6 @@
# Churow Park <churow@naver.com>, 2020
# Jiyoon, Ha <cryptography@konkuk.ac.kr>, 2016
# DaHae Sung <sdh4513136@hanmail.net>, 2016
# 코딩 영, 2021
# Geonho Kim / Leo Kim <gh.leokim@gmail.com>, 2019
# Ian Y. Choi <ianyrchoi@gmail.com>, 2015
# Jannis Leidel <jannis@leidel.info>, 2011
@@ -20,9 +19,9 @@ msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-11-10 02:12+0000\n"
"Last-Translator: 코딩 영\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2020-05-07 08:17+0000\n"
"Last-Translator: Churow Park <churow@naver.com>\n"
"Language-Team: Korean (http://www.transifex.com/django/django/language/ko/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -146,9 +145,6 @@ msgstr "워크 팩터"
msgid "checksum"
msgstr "체크섬"
msgid "block size"
msgstr "블록 크기"
msgid "name"
msgstr "이름"
@@ -1,14 +1,14 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Soyuzbek Orozbek uulu <soyuzbek196.kg@gmail.com>, 2020-2021
# Soyuzbek Orozbek uulu <soyuzbek196.kg@gmail.com>, 2020
# Soyuzbek Orozbek uulu <soyuzbek196.kg@gmail.com>, 2020
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-11-27 14:11+0000\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2020-11-23 00:49+0000\n"
"Last-Translator: Soyuzbek Orozbek uulu <soyuzbek196.kg@gmail.com>\n"
"Language-Team: Kyrgyz (http://www.transifex.com/django/django/language/ky/)\n"
"MIME-Version: 1.0\n"
@@ -133,9 +133,6 @@ msgstr "жумуш фактору"
msgid "checksum"
msgstr "текшерүү"
msgid "block size"
msgstr "блок өлчөмү"
msgid "name"
msgstr "аты"
@@ -4,14 +4,14 @@
# NullIsNot0 <nullisnot0@inbox.lv>, 2017
# NullIsNot0 <nullisnot0@inbox.lv>, 2017
# Jannis Leidel <jannis@leidel.info>, 2011
# NullIsNot0 <nullisnot0@inbox.lv>, 2019,2021
# NullIsNot0 <nullisnot0@inbox.lv>, 2019
# peterisb <pb@sungis.lv>, 2016
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-10-06 05:08+0000\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2019-11-07 07:27+0000\n"
"Last-Translator: NullIsNot0 <nullisnot0@inbox.lv>\n"
"Language-Team: Latvian (http://www.transifex.com/django/django/language/"
"lv/)\n"
@@ -139,9 +139,6 @@ msgstr "darba faktors"
msgid "checksum"
msgstr "kontrolsumma"
msgid "block size"
msgstr "bloka izmērs"
msgid "name"
msgstr "nosaukums"
@@ -1,313 +0,0 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Jafry Hisham, 2021
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-11-16 14:43+0000\n"
"Last-Translator: Jafry Hisham\n"
"Language-Team: Malay (http://www.transifex.com/django/django/language/ms/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ms\n"
"Plural-Forms: nplurals=1; plural=0;\n"
msgid "Personal info"
msgstr "Info peribadi"
msgid "Permissions"
msgstr "Kebenaran"
msgid "Important dates"
msgstr "Tarikh-tarikh penting"
#, python-format
msgid "%(name)s object with primary key %(key)r does not exist."
msgstr "Objek %(name)s dengan kunci utama %(key)r tidak wujud."
msgid "Password changed successfully."
msgstr "Kata laluan berjaya ditukar."
#, python-format
msgid "Change password: %s"
msgstr "Tukar kata laluan: %s"
msgid "Authentication and Authorization"
msgstr "Pengesahan dan Kebenaran"
msgid "password"
msgstr "kata laluan"
msgid "last login"
msgstr "log masuk terakhir"
msgid "No password set."
msgstr "Kata laluan tidak ditetapkan."
msgid "Invalid password format or unknown hashing algorithm."
msgstr ""
"Format kata laluan tidak sah atau algoritma hash yang tidak dapat dipastikan."
msgid "The two password fields didnt match."
msgstr "Medan kedua-dua kata laluan tidak sepadan."
msgid "Password"
msgstr "Kata laluan"
msgid "Password confirmation"
msgstr "Pengesahan kata laluan"
msgid "Enter the same password as before, for verification."
msgstr ""
"Masukkan kata laluan yang sama seperti sebelumnya, bagi tujuan pengesahan."
msgid ""
"Raw passwords are not stored, so there is no way to see this users "
"password, but you can change the password using <a href=\"{}\">this form</a>."
msgstr ""
"Kata laluan mentah tidak disimpan, maka tiada cara untuk melihat kata laluan "
"pengguna, tetapi anda boleh menukar kata laluan menggunakan <a href="
"\"{}\">borang ini</a>."
#, python-format
msgid ""
"Please enter a correct %(username)s and password. Note that both fields may "
"be case-sensitive."
msgstr ""
"Sila masukkan %(username)s dan kata laluan yang betul. Ambil perhatian "
"bahawa kedua-dua medan berkemungkinan kes-sensitif. "
msgid "This account is inactive."
msgstr "Akaun ini tidak aktif."
msgid "Email"
msgstr "Emel"
msgid "New password"
msgstr "Kata laluan baru"
msgid "New password confirmation"
msgstr "Pengesahan kata laluan baru"
msgid "Your old password was entered incorrectly. Please enter it again."
msgstr ""
"Kata laluan lama anda tidak dimasukkan dengan betul. Sila masukkan sekali "
"lagi."
msgid "Old password"
msgstr "Kata laluan lama"
msgid "Password (again)"
msgstr "Kata laluan (sekali lagi)"
msgid "algorithm"
msgstr "algortima"
msgid "iterations"
msgstr "lelaran"
msgid "salt"
msgstr "garam"
msgid "hash"
msgstr "hash"
msgid "variety"
msgstr "kepelbagaian"
msgid "version"
msgstr "versi"
msgid "memory cost"
msgstr "kos memori"
msgid "time cost"
msgstr "kos masa"
msgid "parallelism"
msgstr "parallelisma"
msgid "work factor"
msgstr "faktor kerja"
msgid "checksum"
msgstr "checksum"
msgid "block size"
msgstr "saiz blok"
msgid "name"
msgstr "nama"
msgid "content type"
msgstr "jenis kandungan"
msgid "codename"
msgstr "nama kod"
msgid "permission"
msgstr "kebenaran"
msgid "permissions"
msgstr "kebenaran"
msgid "group"
msgstr "kumpulan"
msgid "groups"
msgstr "kumpulan-kumpulan"
msgid "superuser status"
msgstr "status pengguna hebat"
msgid ""
"Designates that this user has all permissions without explicitly assigning "
"them."
msgstr ""
"Menentukan bahawa pengguna ini mempunyai semua kebenaran tanpa memberikannya "
"secara eksplisit."
msgid ""
"The groups this user belongs to. A user will get all permissions granted to "
"each of their groups."
msgstr ""
"Pengguna ini adalah ahli kepada kumpulan-kumpulan ini. Pengguna akan "
"mewarisi semua kebenaran yang diberikan kepada kumpulan-kumpulan ini."
msgid "user permissions"
msgstr "kebenaran penguna"
msgid "Specific permissions for this user."
msgstr "Kebenaran-kebenaran spesifik bagi pengguna ini."
msgid "username"
msgstr "nama pengguna"
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
msgstr ""
"Diperlukan. 150 karakter atau kurang. Huruf, digit dan @/./+/-/_ sahaja."
msgid "A user with that username already exists."
msgstr "Pengguna dengan nama pengguna ini sudah wujud."
msgid "first name"
msgstr "nama pertama"
msgid "last name"
msgstr "nama akhir"
msgid "email address"
msgstr "alamat emel"
msgid "staff status"
msgstr "status staf"
msgid "Designates whether the user can log into this admin site."
msgstr "Menentukan samada pengguna ini boleh log masuk ke laman pentadbiran."
msgid "active"
msgstr "aktif"
msgid ""
"Designates whether this user should be treated as active. Unselect this "
"instead of deleting accounts."
msgstr ""
"Menentukan samada pengguna ini patut dilayan sebagai aktif. Padam pilihan "
"ini daripada menghapuskan terus akaun pengguna."
msgid "date joined"
msgstr "tarikh serta"
msgid "user"
msgstr "pengguna"
msgid "users"
msgstr "pengguna-pengguna"
#, python-format
msgid ""
"This password is too short. It must contain at least %(min_length)d "
"character."
msgid_plural ""
"This password is too short. It must contain at least %(min_length)d "
"characters."
msgstr[0] ""
"Kata laluan ini terlalu singkat. Ia harus mempunyai sekurang-kurangnya "
"%(min_length)d karakter."
#, python-format
msgid "Your password must contain at least %(min_length)d character."
msgid_plural "Your password must contain at least %(min_length)d characters."
msgstr[0] ""
"Kata laluan anda harus mempunyai sekurang-kurangnya %(min_length)d karakter."
#, python-format
msgid "The password is too similar to the %(verbose_name)s."
msgstr "Kata laluan ini hampir sama dengan %(verbose_name)s."
msgid "Your password cant be too similar to your other personal information."
msgstr ""
"Kata laluan anda tidak boleh hampir sama dengan maklumat peribadi anda yang "
"lain."
msgid "This password is too common."
msgstr "Kata laluan anda terlalu singkat."
msgid "Your password cant be a commonly used password."
msgstr ""
"Kata laluan anda tidak boleh sama dengan kata laluan yang terlalu biasa "
"digunakan."
msgid "This password is entirely numeric."
msgstr "Aksara kata laluan ini kesemuanya terdiri daripada nombor."
msgid "Your password cant be entirely numeric."
msgstr ""
"Kata laluan anda tidak boleh terdiri daripada aksara nombor secara "
"sepenuhnya."
#, python-format
msgid "Password reset on %(site_name)s"
msgstr "Penetapan semula kata laluan di %(site_name)s"
msgid ""
"Enter a valid username. This value may contain only English letters, "
"numbers, and @/./+/-/_ characters."
msgstr ""
"Masukkan nama pengguna yang sah. Nilai ini hanya boleh mengandungi huruf "
"bahasa Inggeris, nombor, dan karakter-karakter @/./+/-/_ ."
msgid ""
"Enter a valid username. This value may contain only letters, numbers, and "
"@/./+/-/_ characters."
msgstr ""
"Masukkan nama pengguna yang sah. Nilai boleh mengandungi huruf, mombor, dan "
"karakter-karakter @/./+/-/_ ."
msgid "Logged out"
msgstr "Telah di log keluar"
msgid "Password reset"
msgstr "Penetapan semula kata laluan"
msgid "Password reset sent"
msgstr "Penetapan semula kata laluan telah dihantar"
msgid "Enter new password"
msgstr "Masukkan kata laluan yang baru"
msgid "Password reset unsuccessful"
msgstr "Penetapan semula kata laluan tidak berjaya "
msgid "Password reset complete"
msgstr "Penetapan semula kata laluan telah lengkap"
msgid "Password change"
msgstr "Penukaran kata laluan"
msgid "Password change successful"
msgstr "Penukaran kata laluan berjaya dilakukan"
@@ -2,14 +2,13 @@
#
# Translators:
# Jannis Leidel <jannis@leidel.info>, 2011
# Sivert Olstad, 2021
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-11-12 08:38+0000\n"
"Last-Translator: Sivert Olstad\n"
"POT-Creation-Date: 2017-09-24 13:46+0200\n"
"PO-Revision-Date: 2017-09-24 14:24+0000\n"
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
"Language-Team: Norwegian Nynorsk (http://www.transifex.com/django/django/"
"language/nn/)\n"
"MIME-Version: 1.0\n"
@@ -29,7 +28,7 @@ msgstr "Viktige datoar"
#, python-format
msgid "%(name)s object with primary key %(key)r does not exist."
msgstr "%(name)s-objekt med primærnøkkelen %(key)r eksisterer ikkje."
msgstr ""
msgid "Password changed successfully."
msgstr "Passordet er endra."
@@ -39,7 +38,7 @@ msgid "Change password: %s"
msgstr "Endre passord: %s"
msgid "Authentication and Authorization"
msgstr "Stadfesting og Autorisasjon"
msgstr ""
msgid "password"
msgstr "passord"
@@ -48,12 +47,12 @@ msgid "last login"
msgstr "siste innlogging"
msgid "No password set."
msgstr "Passord ikkje sett."
msgstr ""
msgid "Invalid password format or unknown hashing algorithm."
msgstr "Ugyldig passordformat eller ukjend hash-algoritme."
msgstr ""
msgid "The two password fields didnt match."
msgid "The two password fields didn't match."
msgstr "Dei to passordfelta er ikkje like."
msgid "Password"
@@ -63,29 +62,24 @@ msgid "Password confirmation"
msgstr "Stadfesting av passord"
msgid "Enter the same password as before, for verification."
msgstr "Skriv inn det samme passordet som før, for verifisering."
msgstr ""
msgid ""
"Raw passwords are not stored, so there is no way to see this users "
"Raw passwords are not stored, so there is no way to see this user's "
"password, but you can change the password using <a href=\"{}\">this form</a>."
msgstr ""
"Sjølve passordet vert ikkje lagra, så det finnast ingen måte å sjå denne "
"brukaren sitt passord, men du kan endra passordet med <a href=\"{}\">dette "
"skjemaet</a>."
#, python-format
msgid ""
"Please enter a correct %(username)s and password. Note that both fields may "
"be case-sensitive."
msgstr ""
"Oppgje korrekt %(username)s og passord. Merk at det er skilnad på små og "
"store bokstavar."
msgid "This account is inactive."
msgstr "Denne kontoen er inaktiv."
msgid "Email"
msgstr "E-post"
msgstr ""
msgid "New password"
msgstr "Nytt passord"
@@ -103,46 +97,43 @@ msgid "Password (again)"
msgstr "Passord (gjenta)"
msgid "algorithm"
msgstr "algoritme"
msgstr ""
msgid "iterations"
msgstr "iterasjonar"
msgstr ""
msgid "salt"
msgstr "salt"
msgstr ""
msgid "hash"
msgstr "hash"
msgstr ""
msgid "variety"
msgstr "variasjon"
msgstr ""
msgid "version"
msgstr "versjon"
msgstr ""
msgid "memory cost"
msgstr "minnekostnad"
msgstr ""
msgid "time cost"
msgstr "tidskostnad"
msgstr ""
msgid "parallelism"
msgstr "parallellitet"
msgstr ""
msgid "work factor"
msgstr "arbeidsfaktor"
msgstr ""
msgid "checksum"
msgstr "kontrollsum"
msgid "block size"
msgstr "blokkstorleik"
msgstr ""
msgid "name"
msgstr "namn"
msgid "content type"
msgstr "innhaldstype"
msgstr ""
msgid "codename"
msgstr "kodenamn"
@@ -171,19 +162,18 @@ msgid ""
"The groups this user belongs to. A user will get all permissions granted to "
"each of their groups."
msgstr ""
"Gruppene brukaren tilhøyrer. Brukarar får løyva til gruppene dei er med i."
msgid "user permissions"
msgstr "Brukerløyve"
msgid "Specific permissions for this user."
msgstr "Løyva til denne brukaren."
msgstr ""
msgid "username"
msgstr "brukarnamn"
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
msgstr "Nødvendig. 150 teikn eller færre. Berre bokstavar, tall @/./+/-/_."
msgstr ""
msgid "A user with that username already exists."
msgstr "Det eksisterar allereie ein brukar med dette brukernamnet."
@@ -195,7 +185,7 @@ msgid "last name"
msgstr "etternamn"
msgid "email address"
msgstr "e-postadresse"
msgstr ""
msgid "staff status"
msgstr "administrasjonsstatus"
@@ -229,73 +219,67 @@ msgid_plural ""
"This password is too short. It must contain at least %(min_length)d "
"characters."
msgstr[0] ""
"Dette passordet er for stutt. Det må innehalde minst %(min_length)d teikn."
msgstr[1] ""
"Dette passordet er for stutt. Det må innehalde minst %(min_length)d teikn."
#, python-format
msgid "Your password must contain at least %(min_length)d character."
msgid_plural "Your password must contain at least %(min_length)d characters."
msgstr[0] "Passordet ditt må innehalde minst %(min_length)d teikn."
msgstr[1] "Passordet ditt må innehalde minst %(min_length)d teikn."
msgstr[0] ""
msgstr[1] ""
#, python-format
msgid "The password is too similar to the %(verbose_name)s."
msgstr "Passordet er for likt %(verbose_name)s."
msgstr ""
msgid "Your password cant be too similar to your other personal information."
msgstr "Passordet ditt kan ikkje vere for likt dine andre personopplysingar."
msgid "Your password can't be too similar to your other personal information."
msgstr ""
msgid "This password is too common."
msgstr "Dette passordet er for vanleg."
msgstr ""
msgid "Your password cant be a commonly used password."
msgstr "Passordet ditt kan ikkje vere eit ofte brukt passord."
msgid "Your password can't be a commonly used password."
msgstr ""
msgid "This password is entirely numeric."
msgstr "Dette passordet innehalder berre tal."
msgstr ""
msgid "Your password cant be entirely numeric."
msgstr "Passordet ditt kan ikkje innehalde berre tal."
msgid "Your password can't be entirely numeric."
msgstr ""
#, python-format
msgid "Password reset on %(site_name)s"
msgstr "Passordnullstilling på %(site_name)s"
msgstr ""
msgid ""
"Enter a valid username. This value may contain only English letters, "
"numbers, and @/./+/-/_ characters."
msgstr ""
"Oppgje eit gyldig brukarnamn. Denne verdien kan berre innehalde bokstavar, "
"tal, og @/./+/-/_ teikn."
msgid ""
"Enter a valid username. This value may contain only letters, numbers, and "
"@/./+/-/_ characters."
msgstr ""
"Oppgje eit gyldig brukarnamn. Denne verdien kan berre innehalde bokstavar, "
"tal, og @/./+/-/_ teikn."
msgid "Logged out"
msgstr "Logga ut"
msgid "Password reset"
msgstr "Nullstill passord"
msgstr ""
msgid "Password reset sent"
msgstr "Passordnullstilling utsendt"
msgstr ""
msgid "Enter new password"
msgstr "Oppgje nytt passord"
msgstr ""
msgid "Password reset unsuccessful"
msgstr "Passordet vart ikkje nullstilt"
msgstr ""
msgid "Password reset complete"
msgstr "Passord nullstilt"
msgstr ""
msgid "Password change"
msgstr "Endre passord"
msgstr ""
msgid "Password change successful"
msgstr "Passord endra"
msgstr ""
@@ -6,10 +6,10 @@
# Janusz Harkot <jh@trilab.pl>, 2015
# Karol <kfuks2@o2.pl>, 2012
# m_aciek <maciej.olko@gmail.com>, 2014
# m_aciek <maciej.olko@gmail.com>, 2016-2017,2019,2021
# m_aciek <maciej.olko@gmail.com>, 2016-2017,2019
# m_aciek <maciej.olko@gmail.com>, 2014-2015
# muszalski <m.muszalski@gmail.com>, 2016
# c10516f0462e552b4c3672569f0745a7_cc5cca2 <841826256cd8f47d0e443806a8e56601_19204>, 2014
# p <inactive+poczciwiec@transifex.com>, 2014
# Mattia Procopio <promat85@gmail.com>, 2014
# Roman Barczyński, 2012
# Tomasz Kajtoch <tomekkaj@tomekkaj.pl>, 2016
@@ -17,8 +17,8 @@ msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-09-22 11:17+0000\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2019-11-06 20:03+0000\n"
"Last-Translator: m_aciek <maciej.olko@gmail.com>\n"
"Language-Team: Polish (http://www.transifex.com/django/django/language/pl/)\n"
"MIME-Version: 1.0\n"
@@ -50,7 +50,7 @@ msgid "Change password: %s"
msgstr "Zmień hasło: %s"
msgid "Authentication and Authorization"
msgstr "Uwierzytelnienie i autoryzacja"
msgstr "Uwierzytelnianie i autoryzacja"
msgid "password"
msgstr "hasło"
@@ -59,7 +59,7 @@ msgid "last login"
msgstr "ostatnie logowanie"
msgid "No password set."
msgstr "Nie ustawiono hasła."
msgstr "Hasło nie zostało ustawione."
msgid "Invalid password format or unknown hashing algorithm."
msgstr ""
@@ -83,7 +83,7 @@ msgid ""
"password, but you can change the password using <a href=\"{}\">this form</a>."
msgstr ""
"Nie przechowujemy surowych haseł, więc nie da się zobaczyć hasła tego "
"użytkownika. Możesz jednak je zmienić używając <a href=\"{}\">tego "
"użytkownika. Możesz jednak zmienić to hasło używając <a href=\"{}\">tego "
"formularza</a>."
#, python-format
@@ -98,7 +98,7 @@ msgid "This account is inactive."
msgstr "To konto jest nieaktywne."
msgid "Email"
msgstr "Adres e-mail"
msgstr "Adres email"
msgid "New password"
msgstr "Nowe hasło"
@@ -148,9 +148,6 @@ msgstr "work factor"
msgid "checksum"
msgstr "suma kontrolna"
msgid "block size"
msgstr "rozmiar bloku"
msgid "name"
msgstr "nazwa"
@@ -196,7 +193,7 @@ msgid "Specific permissions for this user."
msgstr "Szczególne uprawnienia dla tego użytkownika."
msgid "username"
msgstr "nazwa użytkownika"
msgstr "użytkownik"
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
msgstr "Wymagana. 150 lub mniej znaków. Jedynie litery, cyfry i @/./+/-/_."
@@ -211,7 +208,7 @@ msgid "last name"
msgstr "nazwisko"
msgid "email address"
msgstr "adres e-mail"
msgstr "adres email"
msgid "staff status"
msgstr "w zespole"
@@ -301,7 +298,7 @@ msgstr ""
"cyfry i znaki @/./+/-/_."
msgid "Logged out"
msgstr "Wylogowany(-na)"
msgstr "Wylogowany"
msgid "Password reset"
msgstr "Zresetowanie hasła"
@@ -313,10 +310,10 @@ msgid "Enter new password"
msgstr "Wprowadź nowe haslo"
msgid "Password reset unsuccessful"
msgstr "Resetowanie hasła nie powiodło się"
msgstr "Zresetowanie hasła nie powiodło się"
msgid "Password reset complete"
msgstr "Resetowanie hasła zakończone"
msgstr "Zresetowanie hasła powiodło się"
msgid "Password change"
msgstr "Zmiana hasła"
@@ -2,18 +2,16 @@
#
# Translators:
# Jannis Leidel <jannis@leidel.info>, 2011
# 18f25ad6fa9930fc67cb11aca9d16a27, 2012-2014
# Juraj Bubniak <translations@jbub.eu>, 2012-2014
# Marian Andre <marian@andre.sk>, 2015,2017
# Martin Tóth <ezimir@gmail.com>, 2017-2018
# Peter Kuma, 2021
# Richard von Kellner, 2021
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-09-30 09:28+0000\n"
"Last-Translator: Richard von Kellner\n"
"POT-Creation-Date: 2017-09-24 13:46+0200\n"
"PO-Revision-Date: 2018-05-03 06:50+0000\n"
"Last-Translator: Martin Tóth <ezimir@gmail.com>\n"
"Language-Team: Slovak (http://www.transifex.com/django/django/language/sk/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -57,7 +55,7 @@ msgstr "Žiadne heslo."
msgid "Invalid password format or unknown hashing algorithm."
msgstr "Neplatný formát hesla alebo neznámy hašovací algoritmus."
msgid "The two password fields didnt match."
msgid "The two password fields didn't match."
msgstr "Heslo a jeho potvrdenie sa nezhodujú."
msgid "Password"
@@ -70,7 +68,7 @@ msgid "Enter the same password as before, for verification."
msgstr "Kvôli overeniu, znovu zadajte rovnaké heslo."
msgid ""
"Raw passwords are not stored, so there is no way to see this users "
"Raw passwords are not stored, so there is no way to see this user's "
"password, but you can change the password using <a href=\"{}\">this form</a>."
msgstr ""
"Heslá v pôvodnom tvare nie sú ukladané, takže neexistuje spôsob zobraziť "
@@ -139,9 +137,6 @@ msgstr "faktor práce"
msgid "checksum"
msgstr "kontrolný súčet"
msgid "block size"
msgstr "veľkosť bloku"
msgid "name"
msgstr "meno"
@@ -256,19 +251,19 @@ msgstr[3] "Vaše heslo musí obsahovať aspoň %(min_length)d znakov."
msgid "The password is too similar to the %(verbose_name)s."
msgstr "Heslo sa príliš podobá na %(verbose_name)s."
msgid "Your password cant be too similar to your other personal information."
msgid "Your password can't be too similar to your other personal information."
msgstr "Vaše heslo sa nesmie príliš podobať na ostatné osobné informácie."
msgid "This password is too common."
msgstr "Toto heslo je používané príliš často."
msgid "Your password cant be a commonly used password."
msgstr "Vaše heslo nemôže byť jedno z často používaných hesiel."
msgid "Your password can't be a commonly used password."
msgstr "Vaše heslo nemôže byť jedno z často používaných."
msgid "This password is entirely numeric."
msgstr "Toto heslo pozostáva iba z číslic."
msgid "Your password cant be entirely numeric."
msgid "Your password can't be entirely numeric."
msgstr "Vaše heslo nemôže pozostávať iba z číslic."
#, python-format
@@ -1,15 +1,15 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Igor Jerosimić, 2020-2021
# Igor Jerosimić, 2020
# Jannis Leidel <jannis@leidel.info>, 2011
# Janos Guljas <janos@resenje.org>, 2011-2012
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-09-22 19:36+0000\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2020-01-21 20:38+0000\n"
"Last-Translator: Igor Jerosimić\n"
"Language-Team: Serbian (http://www.transifex.com/django/django/language/"
"sr/)\n"
@@ -137,9 +137,6 @@ msgstr "фактор сложености"
msgid "checksum"
msgstr "сума за проверу"
msgid "block size"
msgstr "величина блока"
msgid "name"
msgstr "име"
@@ -1,18 +1,17 @@
# This file is distributed under the same license as the Django package.
#
# Translators:
# Igor Jerosimić, 2021
# Jannis Leidel <jannis@leidel.info>, 2011
# Janos Guljas <janos@resenje.org>, 2011-2012
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-09-22 09:22+0000\n"
"Last-Translator: Transifex Bot <>\n"
"POT-Creation-Date: 2017-09-24 13:46+0200\n"
"PO-Revision-Date: 2017-09-24 14:24+0000\n"
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
"Language-Team: Serbian (Latin) (http://www.transifex.com/django/django/"
"language/sr@latin/)\n"
"language/sr%40latin/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -50,13 +49,13 @@ msgid "last login"
msgstr "poslednja prijava"
msgid "No password set."
msgstr "Lozinka nije uneta."
msgstr ""
msgid "Invalid password format or unknown hashing algorithm."
msgstr ""
msgid "The two password fields didnt match."
msgstr ""
msgid "The two password fields didn't match."
msgstr "Dva polja za lozinke se nisu poklopila."
msgid "Password"
msgstr "Lozinka"
@@ -68,7 +67,7 @@ msgid "Enter the same password as before, for verification."
msgstr ""
msgid ""
"Raw passwords are not stored, so there is no way to see this users "
"Raw passwords are not stored, so there is no way to see this user's "
"password, but you can change the password using <a href=\"{}\">this form</a>."
msgstr ""
@@ -132,9 +131,6 @@ msgstr "faktor rada"
msgid "checksum"
msgstr "suma za proveru"
msgid "block size"
msgstr ""
msgid "name"
msgstr "ime"
@@ -243,19 +239,19 @@ msgstr[2] ""
msgid "The password is too similar to the %(verbose_name)s."
msgstr ""
msgid "Your password cant be too similar to your other personal information."
msgid "Your password can't be too similar to your other personal information."
msgstr ""
msgid "This password is too common."
msgstr ""
msgid "Your password cant be a commonly used password."
msgid "Your password can't be a commonly used password."
msgstr ""
msgid "This password is entirely numeric."
msgstr ""
msgid "Your password cant be entirely numeric."
msgid "Your password can't be entirely numeric."
msgstr ""
#, python-format
@@ -2,7 +2,7 @@
#
# Translators:
# Ahmet Emre Aladağ <emre.aladag@isik.edu.tr>, 2013
# BouRock, 2015-2017,2019-2021
# BouRock, 2015-2017,2019-2020
# BouRock, 2014-2015
# Caner Başaran <basaran.caner@gmail.com>, 2013
# Cihad GÜNDOĞDU <cihadgundogdu@gmail.com>, 2014
@@ -15,8 +15,8 @@ msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-09-22 17:30+0000\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2020-09-29 18:52+0000\n"
"Last-Translator: BouRock\n"
"Language-Team: Turkish (http://www.transifex.com/django/django/language/"
"tr/)\n"
@@ -143,9 +143,6 @@ msgstr "iş faktörü"
msgid "checksum"
msgstr "sağlama"
msgid "block size"
msgstr "blok boyutu"
msgid "name"
msgstr "adı"
@@ -8,7 +8,7 @@
# Kirill Gagarski <gagarin.gtn@gmail.com>, 2015
# Max V. Stotsky <transifex@ms.pereslavl.ru>, 2014
# captain_m4l <qotsaman@gmail.com>, 2012
# Mykola Zamkovoi <nickzam@gmail.com>, 2014,2021
# Mykola Zamkovoi <nickzam@gmail.com>, 2014
# Alex Bolotov <oleksandr.bolotov@gmail.com>, 2013
# Vitaliy Kozlovskyi <ubombi@gmail.com>, 2015
# Zoriana Zaiats, 2016-2017
@@ -16,9 +16,9 @@ msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-10-23 17:41+0000\n"
"Last-Translator: Mykola Zamkovoi <nickzam@gmail.com>\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2021-01-19 23:40+0000\n"
"Last-Translator: Illia Volochii <illia.volochii@gmail.com>\n"
"Language-Team: Ukrainian (http://www.transifex.com/django/django/language/"
"uk/)\n"
"MIME-Version: 1.0\n"
@@ -31,7 +31,7 @@ msgstr ""
"(n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\n"
msgid "Personal info"
msgstr "Особиста інформація"
msgstr "Персональна інформація"
msgid "Permissions"
msgstr "Дозволи"
@@ -148,9 +148,6 @@ msgstr "робочий фактор"
msgid "checksum"
msgstr "контрольна сума"
msgid "block size"
msgstr "розмір блоку"
msgid "name"
msgstr "ім'я"
@@ -5,15 +5,14 @@
# Anh Phan <vietnamesel10n@gmail.com>, 2013
# Tran <hongdiepkien@gmail.com>, 2011
# Tran Van <vantxm@yahoo.co.uk>, 2012
# tinnguyen121221, 2021
# xgenvn <xgenvn@gmail.com>, 2014
msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-12-23 17:49+0000\n"
"Last-Translator: tinnguyen121221\n"
"POT-Creation-Date: 2017-09-24 13:46+0200\n"
"PO-Revision-Date: 2017-09-24 14:24+0000\n"
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
"Language-Team: Vietnamese (http://www.transifex.com/django/django/language/"
"vi/)\n"
"MIME-Version: 1.0\n"
@@ -33,7 +32,7 @@ msgstr "Những ngày quan trọng"
#, python-format
msgid "%(name)s object with primary key %(key)r does not exist."
msgstr " Đối tượng %(name)s với khóa chính %(key)r không tồn tại."
msgstr ""
msgid "Password changed successfully."
msgstr "Mật khẩu thay đổi thành công"
@@ -57,8 +56,8 @@ msgstr "Chưa đặt mật khẩu"
msgid "Invalid password format or unknown hashing algorithm."
msgstr "Định dạng của mật khẩu không đúng hoặc thuật toán hash chưa rõ ràng."
msgid "The two password fields didnt match."
msgstr "Hai trường mật khẩu không giống nhau."
msgid "The two password fields didn't match."
msgstr "Hai trường mật khẩu không giống nhau"
msgid "Password"
msgstr "Mật khẩu"
@@ -67,15 +66,12 @@ msgid "Password confirmation"
msgstr "Xác nhận mật khẩu"
msgid "Enter the same password as before, for verification."
msgstr "Nhập lại mật khẩu để xác nhận."
msgstr ""
msgid ""
"Raw passwords are not stored, so there is no way to see this users "
"Raw passwords are not stored, so there is no way to see this user's "
"password, but you can change the password using <a href=\"{}\">this form</a>."
msgstr ""
"Mật khẩu không được lưu trữ, vì vậy không có cách nào để xem mật khẩu của "
"người dùng, nhưng bạn có thể dùng <a href=\"{}\">biểu mẫu</a> này để thay "
"đổi."
#, python-format
msgid ""
@@ -119,19 +115,19 @@ msgid "hash"
msgstr "băm"
msgid "variety"
msgstr "variety"
msgstr ""
msgid "version"
msgstr "version"
msgstr ""
msgid "memory cost"
msgstr "memory cost"
msgstr ""
msgid "time cost"
msgstr "time cost"
msgstr ""
msgid "parallelism"
msgstr "parallelism"
msgstr ""
msgid "work factor"
msgstr "yếu tố công việc"
@@ -139,14 +135,11 @@ msgstr "yếu tố công việc"
msgid "checksum"
msgstr "kiểm tra"
msgid "block size"
msgstr "block size"
msgid "name"
msgstr "Tên"
msgid "content type"
msgstr "kiểu nội dung"
msgstr ""
msgid "codename"
msgstr "tên mã"
@@ -177,8 +170,6 @@ msgid ""
"The groups this user belongs to. A user will get all permissions granted to "
"each of their groups."
msgstr ""
"Các nhóm người dùng này thuộc về. Người dùng sẽ nhận được tất cả các quyền "
"được cấp cho mỗi nhóm."
msgid "user permissions"
msgstr "quyền của người sử dụng"
@@ -190,7 +181,7 @@ msgid "username"
msgstr "Tên đăng nhập"
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
msgstr "Yêu cầu. 150 ký tự hoặc ít hơn. Chỉ là chữ cái, chữ số và @/./+/-/_."
msgstr ""
msgid "A user with that username already exists."
msgstr "Tên đăng nhập đã được sử dụng"
@@ -236,31 +227,31 @@ msgid ""
msgid_plural ""
"This password is too short. It must contain at least %(min_length)d "
"characters."
msgstr[0] "Mật khẩu quá ngắn. Nó phải chứa ít nhất %(min_length)d ký tự."
msgstr[0] ""
#, python-format
msgid "Your password must contain at least %(min_length)d character."
msgid_plural "Your password must contain at least %(min_length)d characters."
msgstr[0] "Mật khẩu của bạn phải chứa ít nhất %(min_length)d ký tự."
msgstr[0] ""
#, python-format
msgid "The password is too similar to the %(verbose_name)s."
msgstr "Mật khẩu quá giống với %(verbose_name)s."
msgstr ""
msgid "Your password cant be too similar to your other personal information."
msgstr "Mật khẩu của bạn không được quá giống với thông tin cá nhân khác."
msgid "Your password can't be too similar to your other personal information."
msgstr ""
msgid "This password is too common."
msgstr "Mật khẩu này quá phổ biến."
msgstr ""
msgid "Your password cant be a commonly used password."
msgstr "Mật khẩu của bạn không được là mật khẩu được dùng phổ biến."
msgid "Your password can't be a commonly used password."
msgstr ""
msgid "This password is entirely numeric."
msgstr "Mật khẩu này hoàn toàn là số."
msgstr ""
msgid "Your password cant be entirely numeric."
msgstr "Mật khẩu của bạn không được hoàn toàn bằng số."
msgid "Your password can't be entirely numeric."
msgstr ""
#, python-format
msgid "Password reset on %(site_name)s"
@@ -270,15 +261,11 @@ msgid ""
"Enter a valid username. This value may contain only English letters, "
"numbers, and @/./+/-/_ characters."
msgstr ""
"Điền tên đăng nhập hợp lệ. Giá trị này chỉ có thể chứa các chữ cái tiếng "
"Anh, số, và các ký tự @/./+/-/_"
msgid ""
"Enter a valid username. This value may contain only letters, numbers, and "
"@/./+/-/_ characters."
msgstr ""
"Điền tên đăng nhập hợp lệ. Giá trị này chỉ có thể chứa các chữ cái, số, và "
"các ký tự @/./+/-/_"
msgid "Logged out"
msgstr "Đã thoát"
@@ -287,7 +274,7 @@ msgid "Password reset"
msgstr "Đặt lại mật khẩu"
msgid "Password reset sent"
msgstr "Đã gửi hướng dẫn đặt lại mật khẩu"
msgstr ""
msgid "Enter new password"
msgstr "Nhập mật khẩu mới"
@@ -2,7 +2,6 @@
#
# Translators:
# Bo Li <bo.li@measureofquality.com>, 2020
# lanbla <lanlinwen@buaa.edu.cn>, 2021
# David <huangtao0202@gmail.com>, 2019
# ausaki <www.ljm969087551@qq.com>, 2017
# jamin M <lxxmbyx@163.com>, 2019
@@ -11,7 +10,7 @@
# Lele Long <schemacs@gmail.com>, 2011,2015
# Liping Wang <lynn.config@gmail.com>, 2016-2017
# mozillazg <opensource.mozillazg@gmail.com>, 2016
# Lemon Li <leeway1985@gmail.com>, 2012-2013
# pylemon <leeway1985@gmail.com>, 2012-2013
# Wentao Han <wentao.han@gmail.com>, 2020
# hizyn <zhangyanan5552@gmail.com>, 2016
# ced773123cfad7b4e8b79ca80f736af9, 2011
@@ -20,9 +19,9 @@ msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-21 10:22+0200\n"
"PO-Revision-Date: 2021-11-22 03:12+0000\n"
"Last-Translator: lanbla <lanlinwen@buaa.edu.cn>\n"
"POT-Creation-Date: 2019-09-08 17:27+0200\n"
"PO-Revision-Date: 2020-12-09 02:32+0000\n"
"Last-Translator: Bo Li <bo.li@measureofquality.com>\n"
"Language-Team: Chinese (China) (http://www.transifex.com/django/django/"
"language/zh_CN/)\n"
"MIME-Version: 1.0\n"
@@ -145,9 +144,6 @@ msgstr "加密因子"
msgid "checksum"
msgstr "校验和"
msgid "block size"
msgstr "块大小"
msgid "name"
msgstr "名称"
@@ -25,43 +25,27 @@ def _get_builtin_permissions(opts):
"""
perms = []
for action in opts.default_permissions:
perms.append(
(
get_permission_codename(action, opts),
"Can %s %s" % (action, opts.verbose_name_raw),
)
)
perms.append((
get_permission_codename(action, opts),
'Can %s %s' % (action, opts.verbose_name_raw)
))
return perms
def create_permissions(
app_config,
verbosity=2,
interactive=True,
using=DEFAULT_DB_ALIAS,
apps=global_apps,
**kwargs,
):
def create_permissions(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, apps=global_apps, **kwargs):
if not app_config.models_module:
return
# Ensure that contenttypes are created for this app. Needed if
# 'django.contrib.auth' is in INSTALLED_APPS before
# 'django.contrib.contenttypes'.
create_contenttypes(
app_config,
verbosity=verbosity,
interactive=interactive,
using=using,
apps=apps,
**kwargs,
)
create_contenttypes(app_config, verbosity=verbosity, interactive=interactive, using=using, apps=apps, **kwargs)
app_label = app_config.label
try:
app_config = apps.get_app_config(app_label)
ContentType = apps.get_model("contenttypes", "ContentType")
Permission = apps.get_model("auth", "Permission")
ContentType = apps.get_model('contenttypes', 'ContentType')
Permission = apps.get_model('auth', 'Permission')
except LookupError:
return
@@ -76,9 +60,7 @@ def create_permissions(
for klass in app_config.get_models():
# Force looking up the content types in the current database
# before creating foreign keys to them.
ctype = ContentType.objects.db_manager(using).get_for_model(
klass, for_concrete_model=False
)
ctype = ContentType.objects.db_manager(using).get_for_model(klass, for_concrete_model=False)
ctypes.add(ctype)
for perm in _get_all_permissions(klass._meta):
@@ -87,13 +69,11 @@ def create_permissions(
# Find all the Permissions that have a content_type for a model we're
# looking for. We don't need to check for codenames since we already have
# a list of the ones we're going to create.
all_perms = set(
Permission.objects.using(using)
.filter(
content_type__in=ctypes,
)
.values_list("content_type", "codename")
)
all_perms = set(Permission.objects.using(using).filter(
content_type__in=ctypes,
).values_list(
"content_type", "codename"
))
perms = [
Permission(codename=codename, name=name, content_type=ct)
@@ -117,7 +97,7 @@ def get_system_username():
# KeyError will be raised by os.getpwuid() (called by getuser())
# if there is no corresponding entry in the /etc/passwd file
# (a very restricted chroot environment, for example).
return ""
return ''
return result
@@ -137,25 +117,23 @@ def get_default_username(check_db=True, database=DEFAULT_DB_ALIAS):
# If the User model has been swapped out, we can't make any assumptions
# about the default user name.
if auth_app.User._meta.swapped:
return ""
return ''
default_username = get_system_username()
try:
default_username = (
unicodedata.normalize("NFKD", default_username)
.encode("ascii", "ignore")
.decode("ascii")
.replace(" ", "")
.lower()
unicodedata.normalize('NFKD', default_username)
.encode('ascii', 'ignore').decode('ascii')
.replace(' ', '').lower()
)
except UnicodeDecodeError:
return ""
return ''
# Run the username validator
try:
auth_app.User._meta.get_field("username").run_validators(default_username)
auth_app.User._meta.get_field('username').run_validators(default_username)
except exceptions.ValidationError:
return ""
return ''
# Don't return the default username if it is already taken.
if check_db and default_username:
@@ -166,5 +144,5 @@ def get_default_username(check_db=True, database=DEFAULT_DB_ALIAS):
except auth_app.User.DoesNotExist:
pass
else:
return ""
return ''
return default_username
@@ -22,29 +22,25 @@ class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument(
"username",
nargs="?",
help=(
"Username to change password for; by default, it's the current "
"username."
),
'username', nargs='?',
help='Username to change password for; by default, it\'s the current username.',
)
parser.add_argument(
"--database",
'--database',
default=DEFAULT_DB_ALIAS,
help='Specifies the database to use. Default is "default".',
)
def handle(self, *args, **options):
if options["username"]:
username = options["username"]
if options['username']:
username = options['username']
else:
username = getpass.getuser()
try:
u = UserModel._default_manager.using(options["database"]).get(
**{UserModel.USERNAME_FIELD: username}
)
u = UserModel._default_manager.using(options['database']).get(**{
UserModel.USERNAME_FIELD: username
})
except UserModel.DoesNotExist:
raise CommandError("user '%s' does not exist" % username)
@@ -58,22 +54,20 @@ class Command(BaseCommand):
p1 = self._get_pass()
p2 = self._get_pass("Password (again): ")
if p1 != p2:
self.stdout.write("Passwords do not match. Please try again.")
self.stdout.write('Passwords do not match. Please try again.')
count += 1
# Don't validate passwords that don't match.
continue
try:
validate_password(p2, u)
except ValidationError as err:
self.stderr.write("\n".join(err.messages))
self.stderr.write('\n'.join(err.messages))
count += 1
else:
password_validated = True
if count == MAX_TRIES:
raise CommandError(
"Aborting password change for user '%s' after %s attempts" % (u, count)
)
raise CommandError("Aborting password change for user '%s' after %s attempts" % (u, count))
u.set_password(p1)
u.save()
@@ -18,77 +18,69 @@ class NotRunningInTTYException(Exception):
pass
PASSWORD_FIELD = "password"
PASSWORD_FIELD = 'password'
class Command(BaseCommand):
help = "Used to create a superuser."
help = 'Used to create a superuser.'
requires_migrations_checks = True
stealth_options = ("stdin",)
stealth_options = ('stdin',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.UserModel = get_user_model()
self.username_field = self.UserModel._meta.get_field(
self.UserModel.USERNAME_FIELD
)
self.username_field = self.UserModel._meta.get_field(self.UserModel.USERNAME_FIELD)
def add_arguments(self, parser):
parser.add_argument(
"--%s" % self.UserModel.USERNAME_FIELD,
help="Specifies the login for the superuser.",
'--%s' % self.UserModel.USERNAME_FIELD,
help='Specifies the login for the superuser.',
)
parser.add_argument(
"--noinput",
"--no-input",
action="store_false",
dest="interactive",
'--noinput', '--no-input', action='store_false', dest='interactive',
help=(
"Tells Django to NOT prompt the user for input of any kind. "
"You must use --%s with --noinput, along with an option for "
"any other required field. Superusers created with --noinput will "
"not be able to log in until they're given a valid password."
% self.UserModel.USERNAME_FIELD
'Tells Django to NOT prompt the user for input of any kind. '
'You must use --%s with --noinput, along with an option for '
'any other required field. Superusers created with --noinput will '
'not be able to log in until they\'re given a valid password.' %
self.UserModel.USERNAME_FIELD
),
)
parser.add_argument(
"--database",
'--database',
default=DEFAULT_DB_ALIAS,
help='Specifies the database to use. Default is "default".',
)
for field_name in self.UserModel.REQUIRED_FIELDS:
field = self.UserModel._meta.get_field(field_name)
if field.many_to_many:
if (
field.remote_field.through
and not field.remote_field.through._meta.auto_created
):
if field.remote_field.through and not field.remote_field.through._meta.auto_created:
raise CommandError(
"Required field '%s' specifies a many-to-many "
"relation through model, which is not supported." % field_name
"relation through model, which is not supported."
% field_name
)
else:
parser.add_argument(
"--%s" % field_name,
action="append",
'--%s' % field_name, action='append',
help=(
"Specifies the %s for the superuser. Can be used "
"multiple times." % field_name,
'Specifies the %s for the superuser. Can be used '
'multiple times.' % field_name,
),
)
else:
parser.add_argument(
"--%s" % field_name,
help="Specifies the %s for the superuser." % field_name,
'--%s' % field_name,
help='Specifies the %s for the superuser.' % field_name,
)
def execute(self, *args, **options):
self.stdin = options.get("stdin", sys.stdin) # Used for testing
self.stdin = options.get('stdin', sys.stdin) # Used for testing
return super().execute(*args, **options)
def handle(self, *args, **options):
username = options[self.UserModel.USERNAME_FIELD]
database = options["database"]
database = options['database']
user_data = {}
verbose_field_name = self.username_field.verbose_name
try:
@@ -99,36 +91,26 @@ class Command(BaseCommand):
# If not provided, create the user with an unusable password.
user_data[PASSWORD_FIELD] = None
try:
if options["interactive"]:
if options['interactive']:
# Same as user_data but without many to many fields and with
# foreign keys as fake model instances instead of raw IDs.
fake_user_data = {}
if hasattr(self.stdin, "isatty") and not self.stdin.isatty():
if hasattr(self.stdin, 'isatty') and not self.stdin.isatty():
raise NotRunningInTTYException
default_username = get_default_username(database=database)
if username:
error_msg = self._validate_username(
username, verbose_field_name, database
)
error_msg = self._validate_username(username, verbose_field_name, database)
if error_msg:
self.stderr.write(error_msg)
username = None
elif username == "":
raise CommandError(
"%s cannot be blank." % capfirst(verbose_field_name)
)
elif username == '':
raise CommandError('%s cannot be blank.' % capfirst(verbose_field_name))
# Prompt for username.
while username is None:
message = self._get_input_message(
self.username_field, default_username
)
username = self.get_input_data(
self.username_field, message, default_username
)
message = self._get_input_message(self.username_field, default_username)
username = self.get_input_data(self.username_field, message, default_username)
if username:
error_msg = self._validate_username(
username, verbose_field_name, database
)
error_msg = self._validate_username(username, verbose_field_name, database)
if error_msg:
self.stderr.write(error_msg)
username = None
@@ -136,15 +118,12 @@ class Command(BaseCommand):
user_data[self.UserModel.USERNAME_FIELD] = username
fake_user_data[self.UserModel.USERNAME_FIELD] = (
self.username_field.remote_field.model(username)
if self.username_field.remote_field
else username
if self.username_field.remote_field else username
)
# Prompt for required fields.
for field_name in self.UserModel.REQUIRED_FIELDS:
field = self.UserModel._meta.get_field(field_name)
user_data[field_name] = options[field_name]
if user_data[field_name] is not None:
user_data[field_name] = field.clean(user_data[field_name], None)
while user_data[field_name] is None:
message = self._get_input_message(field)
input_value = self.get_input_data(field, message)
@@ -152,98 +131,74 @@ class Command(BaseCommand):
if field.many_to_many and input_value:
if not input_value.strip():
user_data[field_name] = None
self.stderr.write("Error: This field cannot be blank.")
self.stderr.write('Error: This field cannot be blank.')
continue
user_data[field_name] = [
pk.strip() for pk in input_value.split(",")
]
user_data[field_name] = [pk.strip() for pk in input_value.split(',')]
if not field.many_to_many:
fake_user_data[field_name] = input_value
if not field.many_to_many:
fake_user_data[field_name] = user_data[field_name]
# Wrap any foreign keys in fake model instances.
if field.many_to_one:
fake_user_data[field_name] = field.remote_field.model(
user_data[field_name]
)
# Wrap any foreign keys in fake model instances
if field.many_to_one:
fake_user_data[field_name] = field.remote_field.model(input_value)
# Prompt for a password if the model has one.
while PASSWORD_FIELD in user_data and user_data[PASSWORD_FIELD] is None:
password = getpass.getpass()
password2 = getpass.getpass("Password (again): ")
password2 = getpass.getpass('Password (again): ')
if password != password2:
self.stderr.write("Error: Your passwords didn't match.")
# Don't validate passwords that don't match.
continue
if password.strip() == "":
if password.strip() == '':
self.stderr.write("Error: Blank passwords aren't allowed.")
# Don't validate blank passwords.
continue
try:
validate_password(password2, self.UserModel(**fake_user_data))
except exceptions.ValidationError as err:
self.stderr.write("\n".join(err.messages))
response = input(
"Bypass password validation and create user anyway? [y/N]: "
)
if response.lower() != "y":
self.stderr.write('\n'.join(err.messages))
response = input('Bypass password validation and create user anyway? [y/N]: ')
if response.lower() != 'y':
continue
user_data[PASSWORD_FIELD] = password
else:
# Non-interactive mode.
# Use password from environment variable, if provided.
if (
PASSWORD_FIELD in user_data
and "DJANGO_SUPERUSER_PASSWORD" in os.environ
):
user_data[PASSWORD_FIELD] = os.environ["DJANGO_SUPERUSER_PASSWORD"]
if PASSWORD_FIELD in user_data and 'DJANGO_SUPERUSER_PASSWORD' in os.environ:
user_data[PASSWORD_FIELD] = os.environ['DJANGO_SUPERUSER_PASSWORD']
# Use username from environment variable, if not provided in
# options.
if username is None:
username = os.environ.get(
"DJANGO_SUPERUSER_" + self.UserModel.USERNAME_FIELD.upper()
)
username = os.environ.get('DJANGO_SUPERUSER_' + self.UserModel.USERNAME_FIELD.upper())
if username is None:
raise CommandError(
"You must use --%s with --noinput."
% self.UserModel.USERNAME_FIELD
)
raise CommandError('You must use --%s with --noinput.' % self.UserModel.USERNAME_FIELD)
else:
error_msg = self._validate_username(
username, verbose_field_name, database
)
error_msg = self._validate_username(username, verbose_field_name, database)
if error_msg:
raise CommandError(error_msg)
user_data[self.UserModel.USERNAME_FIELD] = username
for field_name in self.UserModel.REQUIRED_FIELDS:
env_var = "DJANGO_SUPERUSER_" + field_name.upper()
env_var = 'DJANGO_SUPERUSER_' + field_name.upper()
value = options[field_name] or os.environ.get(env_var)
if not value:
raise CommandError(
"You must use --%s with --noinput." % field_name
)
raise CommandError('You must use --%s with --noinput.' % field_name)
field = self.UserModel._meta.get_field(field_name)
user_data[field_name] = field.clean(value, None)
if field.many_to_many and isinstance(user_data[field_name], str):
user_data[field_name] = [
pk.strip() for pk in user_data[field_name].split(",")
]
self.UserModel._default_manager.db_manager(database).create_superuser(
**user_data
)
if options["verbosity"] >= 1:
self.UserModel._default_manager.db_manager(database).create_superuser(**user_data)
if options['verbosity'] >= 1:
self.stdout.write("Superuser created successfully.")
except KeyboardInterrupt:
self.stderr.write("\nOperation cancelled.")
self.stderr.write('\nOperation cancelled.')
sys.exit(1)
except exceptions.ValidationError as e:
raise CommandError("; ".join(e.messages))
raise CommandError('; '.join(e.messages))
except NotRunningInTTYException:
self.stdout.write(
"Superuser creation skipped due to not running in a TTY. "
"You can run `manage.py createsuperuser` in your project "
"to create one manually."
'Superuser creation skipped due to not running in a TTY. '
'You can run `manage.py createsuperuser` in your project '
'to create one manually.'
)
def get_input_data(self, field, message, default=None):
@@ -252,45 +207,38 @@ class Command(BaseCommand):
validation exceptions.
"""
raw_value = input(message)
if default and raw_value == "":
if default and raw_value == '':
raw_value = default
try:
val = field.clean(raw_value, None)
except exceptions.ValidationError as e:
self.stderr.write("Error: %s" % "; ".join(e.messages))
self.stderr.write("Error: %s" % '; '.join(e.messages))
val = None
return val
def _get_input_message(self, field, default=None):
return "%s%s%s: " % (
return '%s%s%s: ' % (
capfirst(field.verbose_name),
" (leave blank to use '%s')" % default if default else "",
" (%s.%s)"
% (
" (leave blank to use '%s')" % default if default else '',
' (%s.%s)' % (
field.remote_field.model._meta.object_name,
field.m2m_target_field_name()
if field.many_to_many
else field.remote_field.field_name,
)
if field.remote_field
else "",
field.m2m_target_field_name() if field.many_to_many else field.remote_field.field_name,
) if field.remote_field else '',
)
def _validate_username(self, username, verbose_field_name, database):
"""Validate username. If invalid, return a string error message."""
if self.username_field.unique:
try:
self.UserModel._default_manager.db_manager(database).get_by_natural_key(
username
)
self.UserModel._default_manager.db_manager(database).get_by_natural_key(username)
except self.UserModel.DoesNotExist:
pass
else:
return "Error: That %s is already taken." % verbose_field_name
return 'Error: That %s is already taken.' % verbose_field_name
if not username:
return "%s cannot be blank." % capfirst(verbose_field_name)
return '%s cannot be blank.' % capfirst(verbose_field_name)
try:
self.username_field.clean(username, None)
except exceptions.ValidationError as e:
return "; ".join(e.messages)
return '; '.join(e.messages)
@@ -7,27 +7,25 @@ from django.utils.functional import SimpleLazyObject
def get_user(request):
if not hasattr(request, "_cached_user"):
if not hasattr(request, '_cached_user'):
request._cached_user = auth.get_user(request)
return request._cached_user
class AuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
if not hasattr(request, "session"):
raise ImproperlyConfigured(
"The Django authentication middleware requires session "
"middleware to be installed. Edit your MIDDLEWARE setting to "
"insert "
"'django.contrib.sessions.middleware.SessionMiddleware' before "
"'django.contrib.auth.middleware.AuthenticationMiddleware'."
)
assert hasattr(request, 'session'), (
"The Django authentication middleware requires session middleware "
"to be installed. Edit your MIDDLEWARE setting to insert "
"'django.contrib.sessions.middleware.SessionMiddleware' before "
"'django.contrib.auth.middleware.AuthenticationMiddleware'."
)
request.user = SimpleLazyObject(lambda: get_user(request))
class RemoteUserMiddleware(MiddlewareMixin):
"""
Middleware for utilizing web-server-provided authentication.
Middleware for utilizing Web-server-provided authentication.
If request.user is not authenticated, then this middleware attempts to
authenticate the username passed in the ``REMOTE_USER`` request header.
@@ -47,14 +45,13 @@ class RemoteUserMiddleware(MiddlewareMixin):
def process_request(self, request):
# AuthenticationMiddleware is required so that request.user exists.
if not hasattr(request, "user"):
if not hasattr(request, 'user'):
raise ImproperlyConfigured(
"The Django remote user auth middleware requires the"
" authentication middleware to be installed. Edit your"
" MIDDLEWARE setting to insert"
" 'django.contrib.auth.middleware.AuthenticationMiddleware'"
" before the RemoteUserMiddleware class."
)
" before the RemoteUserMiddleware class.")
try:
username = request.META[self.header]
except KeyError:
@@ -103,9 +100,7 @@ class RemoteUserMiddleware(MiddlewareMixin):
but only if the user is authenticated via the RemoteUserBackend.
"""
try:
stored_backend = load_backend(
request.session.get(auth.BACKEND_SESSION_KEY, "")
)
stored_backend = load_backend(request.session.get(auth.BACKEND_SESSION_KEY, ''))
except ImportError:
# backend failed to load
auth.logout(request)
@@ -116,7 +111,7 @@ class RemoteUserMiddleware(MiddlewareMixin):
class PersistentRemoteUserMiddleware(RemoteUserMiddleware):
"""
Middleware for web-server provided authentication on logon pages.
Middleware for Web-server provided authentication on logon pages.
Like RemoteUserMiddleware but keeps the user authenticated even if
the header (``REMOTE_USER``) is not found in the request. Useful
@@ -124,5 +119,4 @@ class PersistentRemoteUserMiddleware(RemoteUserMiddleware):
is only expected to happen on some "logon" URL and the rest of
the application wants to use Django's authentication mechanism.
"""
force_logout_if_no_header = False
@@ -7,199 +7,98 @@ from django.utils import timezone
class Migration(migrations.Migration):
dependencies = [
("contenttypes", "__first__"),
('contenttypes', '__first__'),
]
operations = [
migrations.CreateModel(
name="Permission",
name='Permission',
fields=[
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
("name", models.CharField(max_length=50, verbose_name="name")),
(
"content_type",
models.ForeignKey(
to="contenttypes.ContentType",
on_delete=models.CASCADE,
verbose_name="content type",
),
),
("codename", models.CharField(max_length=100, verbose_name="codename")),
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=50, verbose_name='name')),
('content_type', models.ForeignKey(
to='contenttypes.ContentType',
on_delete=models.CASCADE,
to_field='id',
verbose_name='content type',
)),
('codename', models.CharField(max_length=100, verbose_name='codename')),
],
options={
"ordering": [
"content_type__app_label",
"content_type__model",
"codename",
],
"unique_together": {("content_type", "codename")},
"verbose_name": "permission",
"verbose_name_plural": "permissions",
'ordering': ['content_type__app_label', 'content_type__model', 'codename'],
'unique_together': {('content_type', 'codename')},
'verbose_name': 'permission',
'verbose_name_plural': 'permissions',
},
managers=[
("objects", django.contrib.auth.models.PermissionManager()),
('objects', django.contrib.auth.models.PermissionManager()),
],
),
migrations.CreateModel(
name="Group",
name='Group',
fields=[
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
(
"name",
models.CharField(unique=True, max_length=80, verbose_name="name"),
),
(
"permissions",
models.ManyToManyField(
to="auth.Permission", verbose_name="permissions", blank=True
),
),
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(unique=True, max_length=80, verbose_name='name')),
('permissions', models.ManyToManyField(to='auth.Permission', verbose_name='permissions', blank=True)),
],
options={
"verbose_name": "group",
"verbose_name_plural": "groups",
'verbose_name': 'group',
'verbose_name_plural': 'groups',
},
managers=[
("objects", django.contrib.auth.models.GroupManager()),
('objects', django.contrib.auth.models.GroupManager()),
],
),
migrations.CreateModel(
name="User",
name='User',
fields=[
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
("password", models.CharField(max_length=128, verbose_name="password")),
(
"last_login",
models.DateTimeField(
default=timezone.now, verbose_name="last login"
),
),
(
"is_superuser",
models.BooleanField(
default=False,
help_text=(
"Designates that this user has all permissions without "
"explicitly assigning them."
),
verbose_name="superuser status",
),
),
(
"username",
models.CharField(
help_text=(
"Required. 30 characters or fewer. Letters, digits and "
"@/./+/-/_ only."
),
unique=True,
max_length=30,
verbose_name="username",
validators=[validators.UnicodeUsernameValidator()],
),
),
(
"first_name",
models.CharField(
max_length=30, verbose_name="first name", blank=True
),
),
(
"last_name",
models.CharField(
max_length=30, verbose_name="last name", blank=True
),
),
(
"email",
models.EmailField(
max_length=75, verbose_name="email address", blank=True
),
),
(
"is_staff",
models.BooleanField(
default=False,
help_text=(
"Designates whether the user can log into this admin site."
),
verbose_name="staff status",
),
),
(
"is_active",
models.BooleanField(
default=True,
verbose_name="active",
help_text=(
"Designates whether this user should be treated as active. "
"Unselect this instead of deleting accounts."
),
),
),
(
"date_joined",
models.DateTimeField(
default=timezone.now, verbose_name="date joined"
),
),
(
"groups",
models.ManyToManyField(
to="auth.Group",
verbose_name="groups",
blank=True,
related_name="user_set",
related_query_name="user",
help_text=(
"The groups this user belongs to. A user will get all "
"permissions granted to each of their groups."
),
),
),
(
"user_permissions",
models.ManyToManyField(
to="auth.Permission",
verbose_name="user permissions",
blank=True,
help_text="Specific permissions for this user.",
related_name="user_set",
related_query_name="user",
),
),
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(default=timezone.now, verbose_name='last login')),
('is_superuser', models.BooleanField(
default=False,
help_text='Designates that this user has all permissions without explicitly assigning them.',
verbose_name='superuser status'
)),
('username', models.CharField(
help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True,
max_length=30, verbose_name='username',
validators=[validators.UnicodeUsernameValidator()],
)),
('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)),
('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)),
('email', models.EmailField(max_length=75, verbose_name='email address', blank=True)),
('is_staff', models.BooleanField(
default=False, help_text='Designates whether the user can log into this admin site.',
verbose_name='staff status'
)),
('is_active', models.BooleanField(
default=True, verbose_name='active', help_text=(
'Designates whether this user should be treated as active. Unselect this instead of deleting '
'accounts.'
)
)),
('date_joined', models.DateTimeField(default=timezone.now, verbose_name='date joined')),
('groups', models.ManyToManyField(
to='auth.Group', verbose_name='groups', blank=True, related_name='user_set',
related_query_name='user', help_text=(
'The groups this user belongs to. A user will get all permissions granted to each of their '
'groups.'
)
)),
('user_permissions', models.ManyToManyField(
to='auth.Permission', verbose_name='user permissions', blank=True,
help_text='Specific permissions for this user.', related_name='user_set',
related_query_name='user')
),
],
options={
"swappable": "AUTH_USER_MODEL",
"verbose_name": "user",
"verbose_name_plural": "users",
'swappable': 'AUTH_USER_MODEL',
'verbose_name': 'user',
'verbose_name_plural': 'users',
},
managers=[
("objects", django.contrib.auth.models.UserManager()),
('objects', django.contrib.auth.models.UserManager()),
],
),
]
@@ -4,13 +4,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("auth", "0001_initial"),
('auth', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name="permission",
name="name",
field=models.CharField(max_length=255, verbose_name="name"),
model_name='permission',
name='name',
field=models.CharField(max_length=255, verbose_name='name'),
),
]
@@ -4,15 +4,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("auth", "0002_alter_permission_name_max_length"),
('auth', '0002_alter_permission_name_max_length'),
]
operations = [
migrations.AlterField(
model_name="user",
name="email",
field=models.EmailField(
max_length=254, verbose_name="email address", blank=True
),
model_name='user',
name='email',
field=models.EmailField(max_length=254, verbose_name='email address', blank=True),
),
]
@@ -5,24 +5,19 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("auth", "0003_alter_user_email_max_length"),
('auth', '0003_alter_user_email_max_length'),
]
# No database changes; modifies validators and error_messages (#13147).
operations = [
migrations.AlterField(
model_name="user",
name="username",
model_name='user',
name='username',
field=models.CharField(
error_messages={"unique": "A user with that username already exists."},
max_length=30,
error_messages={'unique': 'A user with that username already exists.'}, max_length=30,
validators=[validators.UnicodeUsernameValidator()],
help_text=(
"Required. 30 characters or fewer. Letters, digits and @/./+/-/_ "
"only."
),
unique=True,
verbose_name="username",
help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.',
unique=True, verbose_name='username'
),
),
]
@@ -4,15 +4,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("auth", "0004_alter_user_username_opts"),
('auth', '0004_alter_user_username_opts'),
]
operations = [
migrations.AlterField(
model_name="user",
name="last_login",
field=models.DateTimeField(
null=True, verbose_name="last login", blank=True
),
model_name='user',
name='last_login',
field=models.DateTimeField(null=True, verbose_name='last login', blank=True),
),
]
@@ -4,8 +4,8 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("auth", "0005_alter_user_last_login_null"),
("contenttypes", "0002_remove_content_type_name"),
('auth', '0005_alter_user_last_login_null'),
('contenttypes', '0002_remove_content_type_name'),
]
operations = [
@@ -5,23 +5,20 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("auth", "0006_require_contenttypes_0002"),
('auth', '0006_require_contenttypes_0002'),
]
operations = [
migrations.AlterField(
model_name="user",
name="username",
model_name='user',
name='username',
field=models.CharField(
error_messages={"unique": "A user with that username already exists."},
help_text=(
"Required. 30 characters or fewer. Letters, digits and @/./+/-/_ "
"only."
),
error_messages={'unique': 'A user with that username already exists.'},
help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.',
max_length=30,
unique=True,
validators=[validators.UnicodeUsernameValidator()],
verbose_name="username",
verbose_name='username',
),
),
]
@@ -5,23 +5,20 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("auth", "0007_alter_validators_add_error_messages"),
('auth', '0007_alter_validators_add_error_messages'),
]
operations = [
migrations.AlterField(
model_name="user",
name="username",
model_name='user',
name='username',
field=models.CharField(
error_messages={"unique": "A user with that username already exists."},
help_text=(
"Required. 150 characters or fewer. Letters, digits and @/./+/-/_ "
"only."
),
error_messages={'unique': 'A user with that username already exists.'},
help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.',
max_length=150,
unique=True,
validators=[validators.UnicodeUsernameValidator()],
verbose_name="username",
verbose_name='username',
),
),
]
@@ -4,15 +4,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("auth", "0008_alter_user_username_max_length"),
('auth', '0008_alter_user_username_max_length'),
]
operations = [
migrations.AlterField(
model_name="user",
name="last_name",
field=models.CharField(
blank=True, max_length=150, verbose_name="last name"
),
model_name='user',
name='last_name',
field=models.CharField(blank=True, max_length=150, verbose_name='last name'),
),
]
@@ -4,13 +4,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("auth", "0009_alter_user_last_name_max_length"),
('auth', '0009_alter_user_last_name_max_length'),
]
operations = [
migrations.AlterField(
model_name="group",
name="name",
field=models.CharField(max_length=150, unique=True, verbose_name="name"),
model_name='group',
name='name',
field=models.CharField(max_length=150, unique=True, verbose_name='name'),
),
]
@@ -20,26 +20,23 @@ def update_proxy_model_permissions(apps, schema_editor, reverse=False):
of the proxy model.
"""
style = color_style()
Permission = apps.get_model("auth", "Permission")
ContentType = apps.get_model("contenttypes", "ContentType")
Permission = apps.get_model('auth', 'Permission')
ContentType = apps.get_model('contenttypes', 'ContentType')
alias = schema_editor.connection.alias
for Model in apps.get_models():
opts = Model._meta
if not opts.proxy:
continue
proxy_default_permissions_codenames = [
"%s_%s" % (action, opts.model_name) for action in opts.default_permissions
'%s_%s' % (action, opts.model_name)
for action in opts.default_permissions
]
permissions_query = Q(codename__in=proxy_default_permissions_codenames)
for codename, name in opts.permissions:
permissions_query = permissions_query | Q(codename=codename, name=name)
content_type_manager = ContentType.objects.db_manager(alias)
concrete_content_type = content_type_manager.get_for_model(
Model, for_concrete_model=True
)
proxy_content_type = content_type_manager.get_for_model(
Model, for_concrete_model=False
)
concrete_content_type = content_type_manager.get_for_model(Model, for_concrete_model=True)
proxy_content_type = content_type_manager.get_for_model(Model, for_concrete_model=False)
old_content_type = proxy_content_type if reverse else concrete_content_type
new_content_type = concrete_content_type if reverse else proxy_content_type
try:
@@ -49,11 +46,9 @@ def update_proxy_model_permissions(apps, schema_editor, reverse=False):
content_type=old_content_type,
).update(content_type=new_content_type)
except IntegrityError:
old = "{}_{}".format(old_content_type.app_label, old_content_type.model)
new = "{}_{}".format(new_content_type.app_label, new_content_type.model)
sys.stdout.write(
style.WARNING(WARNING.format(old=old, new=new, query=permissions_query))
)
old = '{}_{}'.format(old_content_type.app_label, old_content_type.model)
new = '{}_{}'.format(new_content_type.app_label, new_content_type.model)
sys.stdout.write(style.WARNING(WARNING.format(old=old, new=new, query=permissions_query)))
def revert_proxy_model_permissions(apps, schema_editor):
@@ -66,11 +61,9 @@ def revert_proxy_model_permissions(apps, schema_editor):
class Migration(migrations.Migration):
dependencies = [
("auth", "0010_alter_group_name_max_length"),
("contenttypes", "0002_remove_content_type_name"),
('auth', '0010_alter_group_name_max_length'),
('contenttypes', '0002_remove_content_type_name'),
]
operations = [
migrations.RunPython(
update_proxy_model_permissions, revert_proxy_model_permissions
),
migrations.RunPython(update_proxy_model_permissions, revert_proxy_model_permissions),
]
@@ -4,15 +4,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("auth", "0011_update_proxy_permissions"),
('auth', '0011_update_proxy_permissions'),
]
operations = [
migrations.AlterField(
model_name="user",
name="first_name",
field=models.CharField(
blank=True, max_length=150, verbose_name="first name"
),
model_name='user',
name='first_name',
field=models.CharField(blank=True, max_length=150, verbose_name='first name'),
),
]
@@ -12,9 +12,8 @@ class AccessMixin:
Abstract CBV mixin that gives access mixins the same customizable
functionality.
"""
login_url = None
permission_denied_message = ""
permission_denied_message = ''
raise_exception = False
redirect_field_name = REDIRECT_FIELD_NAME
@@ -25,9 +24,8 @@ class AccessMixin:
login_url = self.login_url or settings.LOGIN_URL
if not login_url:
raise ImproperlyConfigured(
f"{self.__class__.__name__} is missing the login_url attribute. Define "
f"{self.__class__.__name__}.login_url, settings.LOGIN_URL, or override "
f"{self.__class__.__name__}.get_login_url()."
'{0} is missing the login_url attribute. Define {0}.login_url, settings.LOGIN_URL, or override '
'{0}.get_login_url().'.format(self.__class__.__name__)
)
return str(login_url)
@@ -53,8 +51,9 @@ class AccessMixin:
# path as the "next" url.
login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
current_scheme, current_netloc = urlparse(path)[:2]
if (not login_scheme or login_scheme == current_scheme) and (
not login_netloc or login_netloc == current_netloc
if (
(not login_scheme or login_scheme == current_scheme) and
(not login_netloc or login_netloc == current_netloc)
):
path = self.request.get_full_path()
return redirect_to_login(
@@ -66,7 +65,6 @@ class AccessMixin:
class LoginRequiredMixin(AccessMixin):
"""Verify that the current user is authenticated."""
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
@@ -75,7 +73,6 @@ class LoginRequiredMixin(AccessMixin):
class PermissionRequiredMixin(AccessMixin):
"""Verify that the current user has all specified permissions."""
permission_required = None
def get_permission_required(self):
@@ -85,10 +82,8 @@ class PermissionRequiredMixin(AccessMixin):
"""
if self.permission_required is None:
raise ImproperlyConfigured(
f"{self.__class__.__name__} is missing the "
f"permission_required attribute. Define "
f"{self.__class__.__name__}.permission_required, or override "
f"{self.__class__.__name__}.get_permission_required()."
'{0} is missing the permission_required attribute. Define {0}.permission_required, or override '
'{0}.get_permission_required().'.format(self.__class__.__name__)
)
if isinstance(self.permission_required, str):
perms = (self.permission_required,)
@@ -117,9 +112,7 @@ class UserPassesTestMixin(AccessMixin):
def test_func(self):
raise NotImplementedError(
"{} is missing the implementation of the test_func() method.".format(
self.__class__.__name__
)
'{} is missing the implementation of the test_func() method.'.format(self.__class__.__name__)
)
def get_test_func(self):
@@ -19,7 +19,7 @@ def update_last_login(sender, user, **kwargs):
the user logging in.
"""
user.last_login = timezone.now()
user.save(update_fields=["last_login"])
user.save(update_fields=['last_login'])
class PermissionManager(models.Manager):
@@ -28,9 +28,7 @@ class PermissionManager(models.Manager):
def get_by_natural_key(self, codename, app_label, model):
return self.get(
codename=codename,
content_type=ContentType.objects.db_manager(self.db).get_by_natural_key(
app_label, model
),
content_type=ContentType.objects.db_manager(self.db).get_by_natural_key(app_label, model),
)
@@ -57,37 +55,34 @@ class Permission(models.Model):
The permissions listed above are automatically created for each model.
"""
name = models.CharField(_("name"), max_length=255)
name = models.CharField(_('name'), max_length=255)
content_type = models.ForeignKey(
ContentType,
models.CASCADE,
verbose_name=_("content type"),
verbose_name=_('content type'),
)
codename = models.CharField(_("codename"), max_length=100)
codename = models.CharField(_('codename'), max_length=100)
objects = PermissionManager()
class Meta:
verbose_name = _("permission")
verbose_name_plural = _("permissions")
unique_together = [["content_type", "codename"]]
ordering = ["content_type__app_label", "content_type__model", "codename"]
verbose_name = _('permission')
verbose_name_plural = _('permissions')
unique_together = [['content_type', 'codename']]
ordering = ['content_type__app_label', 'content_type__model', 'codename']
def __str__(self):
return "%s | %s" % (self.content_type, self.name)
return '%s | %s' % (self.content_type, self.name)
def natural_key(self):
return (self.codename,) + self.content_type.natural_key()
natural_key.dependencies = ["contenttypes.contenttype"]
natural_key.dependencies = ['contenttypes.contenttype']
class GroupManager(models.Manager):
"""
The manager for the auth's Group model.
"""
use_in_migrations = True
def get_by_natural_key(self, name):
@@ -111,19 +106,18 @@ class Group(models.Model):
members-only portion of your site, or sending them members-only email
messages.
"""
name = models.CharField(_("name"), max_length=150, unique=True)
name = models.CharField(_('name'), max_length=150, unique=True)
permissions = models.ManyToManyField(
Permission,
verbose_name=_("permissions"),
verbose_name=_('permissions'),
blank=True,
)
objects = GroupManager()
class Meta:
verbose_name = _("group")
verbose_name_plural = _("groups")
verbose_name = _('group')
verbose_name_plural = _('groups')
def __str__(self):
return self.name
@@ -140,14 +134,12 @@ class UserManager(BaseUserManager):
Create and save a user with the given username, email, and password.
"""
if not username:
raise ValueError("The given username must be set")
raise ValueError('The given username must be set')
email = self.normalize_email(email)
# Lookup the real model class from the global app registry so this
# manager method can be used in migrations. This is fine because
# managers are by definition working on the real model.
GlobalUserModel = apps.get_model(
self.model._meta.app_label, self.model._meta.object_name
)
GlobalUserModel = apps.get_model(self.model._meta.app_label, self.model._meta.object_name)
username = GlobalUserModel.normalize_username(username)
user = self.model(username=username, email=email, **extra_fields)
user.password = make_password(password)
@@ -155,40 +147,39 @@ class UserManager(BaseUserManager):
return user
def create_user(self, username, email=None, password=None, **extra_fields):
extra_fields.setdefault("is_staff", False)
extra_fields.setdefault("is_superuser", False)
extra_fields.setdefault('is_staff', False)
extra_fields.setdefault('is_superuser', False)
return self._create_user(username, email, password, **extra_fields)
def create_superuser(self, username, email=None, password=None, **extra_fields):
extra_fields.setdefault("is_staff", True)
extra_fields.setdefault("is_superuser", True)
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
if extra_fields.get("is_staff") is not True:
raise ValueError("Superuser must have is_staff=True.")
if extra_fields.get("is_superuser") is not True:
raise ValueError("Superuser must have is_superuser=True.")
if extra_fields.get('is_staff') is not True:
raise ValueError('Superuser must have is_staff=True.')
if extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True.')
return self._create_user(username, email, password, **extra_fields)
def with_perm(
self, perm, is_active=True, include_superusers=True, backend=None, obj=None
):
def with_perm(self, perm, is_active=True, include_superusers=True, backend=None, obj=None):
if backend is None:
backends = auth._get_backends(return_tuples=True)
if len(backends) == 1:
backend, _ = backends[0]
else:
raise ValueError(
"You have multiple authentication backends configured and "
"therefore must provide the `backend` argument."
'You have multiple authentication backends configured and '
'therefore must provide the `backend` argument.'
)
elif not isinstance(backend, str):
raise TypeError(
"backend must be a dotted import path string (got %r)." % backend
'backend must be a dotted import path string (got %r).'
% backend
)
else:
backend = auth.load_backend(backend)
if hasattr(backend, "with_perm"):
if hasattr(backend, 'with_perm'):
return backend.with_perm(
perm,
is_active=is_active,
@@ -201,7 +192,7 @@ class UserManager(BaseUserManager):
# A few helper functions for common logic between User and AnonymousUser.
def _user_get_permissions(user, obj, from_name):
permissions = set()
name = "get_%s_permissions" % from_name
name = 'get_%s_permissions' % from_name
for backend in auth.get_backends():
if hasattr(backend, name):
permissions.update(getattr(backend, name)(user, obj))
@@ -213,7 +204,7 @@ def _user_has_perm(user, perm, obj):
A backend can raise `PermissionDenied` to short-circuit permission checking.
"""
for backend in auth.get_backends():
if not hasattr(backend, "has_perm"):
if not hasattr(backend, 'has_perm'):
continue
try:
if backend.has_perm(user, perm, obj):
@@ -228,7 +219,7 @@ def _user_has_module_perms(user, app_label):
A backend can raise `PermissionDenied` to short-circuit permission checking.
"""
for backend in auth.get_backends():
if not hasattr(backend, "has_module_perms"):
if not hasattr(backend, 'has_module_perms'):
continue
try:
if backend.has_module_perms(user, app_label):
@@ -243,31 +234,30 @@ class PermissionsMixin(models.Model):
Add the fields and methods necessary to support the Group and Permission
models using the ModelBackend.
"""
is_superuser = models.BooleanField(
_("superuser status"),
_('superuser status'),
default=False,
help_text=_(
"Designates that this user has all permissions without "
"explicitly assigning them."
'Designates that this user has all permissions without '
'explicitly assigning them.'
),
)
groups = models.ManyToManyField(
Group,
verbose_name=_("groups"),
verbose_name=_('groups'),
blank=True,
help_text=_(
"The groups this user belongs to. A user will get all permissions "
"granted to each of their groups."
'The groups this user belongs to. A user will get all permissions '
'granted to each of their groups.'
),
related_name="user_set",
related_query_name="user",
)
user_permissions = models.ManyToManyField(
Permission,
verbose_name=_("user permissions"),
verbose_name=_('user permissions'),
blank=True,
help_text=_("Specific permissions for this user."),
help_text=_('Specific permissions for this user.'),
related_name="user_set",
related_query_name="user",
)
@@ -281,7 +271,7 @@ class PermissionsMixin(models.Model):
Query all available auth backends. If an object is passed in,
return only permissions matching this object.
"""
return _user_get_permissions(self, obj, "user")
return _user_get_permissions(self, obj, 'user')
def get_group_permissions(self, obj=None):
"""
@@ -289,10 +279,10 @@ class PermissionsMixin(models.Model):
groups. Query all available auth backends. If an object is passed in,
return only permissions matching this object.
"""
return _user_get_permissions(self, obj, "group")
return _user_get_permissions(self, obj, 'group')
def get_all_permissions(self, obj=None):
return _user_get_permissions(self, obj, "all")
return _user_get_permissions(self, obj, 'all')
def has_perm(self, perm, obj=None):
"""
@@ -335,48 +325,45 @@ class AbstractUser(AbstractBaseUser, PermissionsMixin):
Username and password are required. Other fields are optional.
"""
username_validator = UnicodeUsernameValidator()
username = models.CharField(
_("username"),
_('username'),
max_length=150,
unique=True,
help_text=_(
"Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
),
help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'),
validators=[username_validator],
error_messages={
"unique": _("A user with that username already exists."),
'unique': _("A user with that username already exists."),
},
)
first_name = models.CharField(_("first name"), max_length=150, blank=True)
last_name = models.CharField(_("last name"), max_length=150, blank=True)
email = models.EmailField(_("email address"), blank=True)
first_name = models.CharField(_('first name'), max_length=150, blank=True)
last_name = models.CharField(_('last name'), max_length=150, blank=True)
email = models.EmailField(_('email address'), blank=True)
is_staff = models.BooleanField(
_("staff status"),
_('staff status'),
default=False,
help_text=_("Designates whether the user can log into this admin site."),
help_text=_('Designates whether the user can log into this admin site.'),
)
is_active = models.BooleanField(
_("active"),
_('active'),
default=True,
help_text=_(
"Designates whether this user should be treated as active. "
"Unselect this instead of deleting accounts."
'Designates whether this user should be treated as active. '
'Unselect this instead of deleting accounts.'
),
)
date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
objects = UserManager()
EMAIL_FIELD = "email"
USERNAME_FIELD = "username"
REQUIRED_FIELDS = ["email"]
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
verbose_name = _("user")
verbose_name_plural = _("users")
verbose_name = _('user')
verbose_name_plural = _('users')
abstract = True
def clean(self):
@@ -387,7 +374,7 @@ class AbstractUser(AbstractBaseUser, PermissionsMixin):
"""
Return the first_name plus the last_name, with a space in between.
"""
full_name = "%s %s" % (self.first_name, self.last_name)
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
@@ -406,15 +393,14 @@ class User(AbstractUser):
Username and password are required. Other fields are optional.
"""
class Meta(AbstractUser.Meta):
swappable = "AUTH_USER_MODEL"
swappable = 'AUTH_USER_MODEL'
class AnonymousUser:
id = None
pk = None
username = ""
username = ''
is_staff = False
is_active = False
is_superuser = False
@@ -422,7 +408,7 @@ class AnonymousUser:
_user_permissions = EmptyManager(Permission)
def __str__(self):
return "AnonymousUser"
return 'AnonymousUser'
def __eq__(self, other):
return isinstance(other, self.__class__)
@@ -431,30 +417,19 @@ class AnonymousUser:
return 1 # instances always return the same hash value
def __int__(self):
raise TypeError(
"Cannot cast AnonymousUser to int. Are you trying to use it in place of "
"User?"
)
raise TypeError('Cannot cast AnonymousUser to int. Are you trying to use it in place of User?')
def save(self):
raise NotImplementedError(
"Django doesn't provide a DB representation for AnonymousUser."
)
raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")
def delete(self):
raise NotImplementedError(
"Django doesn't provide a DB representation for AnonymousUser."
)
raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")
def set_password(self, raw_password):
raise NotImplementedError(
"Django doesn't provide a DB representation for AnonymousUser."
)
raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")
def check_password(self, raw_password):
raise NotImplementedError(
"Django doesn't provide a DB representation for AnonymousUser."
)
raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")
@property
def groups(self):
@@ -465,13 +440,13 @@ class AnonymousUser:
return self._user_permissions
def get_user_permissions(self, obj=None):
return _user_get_permissions(self, obj, "user")
return _user_get_permissions(self, obj, 'user')
def get_group_permissions(self, obj=None):
return set()
def get_all_permissions(self, obj=None):
return _user_get_permissions(self, obj, "all")
return _user_get_permissions(self, obj, 'all')
def has_perm(self, perm, obj=None):
return _user_has_perm(self, perm, obj=obj)
@@ -6,15 +6,12 @@ from pathlib import Path
from django.conf import settings
from django.core.exceptions import (
FieldDoesNotExist,
ImproperlyConfigured,
ValidationError,
FieldDoesNotExist, ImproperlyConfigured, ValidationError,
)
from django.utils.functional import cached_property, lazy
from django.utils.functional import lazy
from django.utils.html import format_html, format_html_join
from django.utils.module_loading import import_string
from django.utils.translation import gettext as _
from django.utils.translation import ngettext
from django.utils.translation import gettext as _, ngettext
@functools.lru_cache(maxsize=None)
@@ -26,14 +23,11 @@ def get_password_validators(validator_config):
validators = []
for validator in validator_config:
try:
klass = import_string(validator["NAME"])
klass = import_string(validator['NAME'])
except ImportError:
msg = (
"The module in NAME could not be imported: %s. Check your "
"AUTH_PASSWORD_VALIDATORS setting."
)
raise ImproperlyConfigured(msg % validator["NAME"])
validators.append(klass(**validator.get("OPTIONS", {})))
msg = "The module in NAME could not be imported: %s. Check your AUTH_PASSWORD_VALIDATORS setting."
raise ImproperlyConfigured(msg % validator['NAME'])
validators.append(klass(**validator.get('OPTIONS', {})))
return validators
@@ -65,7 +59,7 @@ def password_changed(password, user=None, password_validators=None):
if password_validators is None:
password_validators = get_default_password_validators()
for validator in password_validators:
password_changed = getattr(validator, "password_changed", lambda *a: None)
password_changed = getattr(validator, 'password_changed', lambda *a: None)
password_changed(password, user)
@@ -87,10 +81,8 @@ def _password_validators_help_text_html(password_validators=None):
in an <ul>.
"""
help_texts = password_validators_help_texts(password_validators)
help_items = format_html_join(
"", "<li>{}</li>", ((help_text,) for help_text in help_texts)
)
return format_html("<ul>{}</ul>", help_items) if help_items else ""
help_items = format_html_join('', '<li>{}</li>', ((help_text,) for help_text in help_texts))
return format_html('<ul>{}</ul>', help_items) if help_items else ''
password_validators_help_text_html = lazy(_password_validators_help_text_html, str)
@@ -100,7 +92,6 @@ class MinimumLengthValidator:
"""
Validate whether the password is of a minimum length.
"""
def __init__(self, min_length=8):
self.min_length = min_length
@@ -108,52 +99,20 @@ class MinimumLengthValidator:
if len(password) < self.min_length:
raise ValidationError(
ngettext(
"This password is too short. It must contain at least "
"%(min_length)d character.",
"This password is too short. It must contain at least "
"%(min_length)d characters.",
self.min_length,
"This password is too short. It must contain at least %(min_length)d character.",
"This password is too short. It must contain at least %(min_length)d characters.",
self.min_length
),
code="password_too_short",
params={"min_length": self.min_length},
code='password_too_short',
params={'min_length': self.min_length},
)
def get_help_text(self):
return ngettext(
"Your password must contain at least %(min_length)d character.",
"Your password must contain at least %(min_length)d characters.",
self.min_length,
) % {"min_length": self.min_length}
def exceeds_maximum_length_ratio(password, max_similarity, value):
"""
Test that value is within a reasonable range of password.
The following ratio calculations are based on testing SequenceMatcher like
this:
for i in range(0,6):
print(10**i, SequenceMatcher(a='A', b='A'*(10**i)).quick_ratio())
which yields:
1 1.0
10 0.18181818181818182
100 0.019801980198019802
1000 0.001998001998001998
10000 0.00019998000199980003
100000 1.999980000199998e-05
This means a length_ratio of 10 should never yield a similarity higher than
0.2, for 100 this is down to 0.02 and for 1000 it is 0.002. This can be
calculated via 2 / length_ratio. As a result we avoid the potentially
expensive sequence matching.
"""
pwd_len = len(password)
length_bound_similarity = max_similarity / 2 * pwd_len
value_len = len(value)
return pwd_len >= 10 * value_len and value_len < length_bound_similarity
self.min_length
) % {'min_length': self.min_length}
class UserAttributeSimilarityValidator:
@@ -167,51 +126,35 @@ class UserAttributeSimilarityValidator:
example, a password is validated against either part of an email address,
as well as the full address.
"""
DEFAULT_USER_ATTRIBUTES = ("username", "first_name", "last_name", "email")
DEFAULT_USER_ATTRIBUTES = ('username', 'first_name', 'last_name', 'email')
def __init__(self, user_attributes=DEFAULT_USER_ATTRIBUTES, max_similarity=0.7):
self.user_attributes = user_attributes
if max_similarity < 0.1:
raise ValueError("max_similarity must be at least 0.1")
self.max_similarity = max_similarity
def validate(self, password, user=None):
if not user:
return
password = password.lower()
for attribute_name in self.user_attributes:
value = getattr(user, attribute_name, None)
if not value or not isinstance(value, str):
continue
value_lower = value.lower()
value_parts = re.split(r"\W+", value_lower) + [value_lower]
value_parts = re.split(r'\W+', value) + [value]
for value_part in value_parts:
if exceeds_maximum_length_ratio(
password, self.max_similarity, value_part
):
continue
if (
SequenceMatcher(a=password, b=value_part).quick_ratio()
>= self.max_similarity
):
if SequenceMatcher(a=password.lower(), b=value_part.lower()).quick_ratio() >= self.max_similarity:
try:
verbose_name = str(
user._meta.get_field(attribute_name).verbose_name
)
verbose_name = str(user._meta.get_field(attribute_name).verbose_name)
except FieldDoesNotExist:
verbose_name = attribute_name
raise ValidationError(
_("The password is too similar to the %(verbose_name)s."),
code="password_too_similar",
params={"verbose_name": verbose_name},
code='password_too_similar',
params={'verbose_name': verbose_name},
)
def get_help_text(self):
return _(
"Your password cant be too similar to your other personal information."
)
return _('Your password cant be too similar to your other personal information.')
class CommonPasswordValidator:
@@ -224,16 +167,11 @@ class CommonPasswordValidator:
https://gist.github.com/roycewilliams/281ce539915a947a23db17137d91aeb7
The password list must be lowercased to match the comparison in validate().
"""
@cached_property
def DEFAULT_PASSWORD_LIST_PATH(self):
return Path(__file__).resolve().parent / "common-passwords.txt.gz"
DEFAULT_PASSWORD_LIST_PATH = Path(__file__).resolve().parent / 'common-passwords.txt.gz'
def __init__(self, password_list_path=DEFAULT_PASSWORD_LIST_PATH):
if password_list_path is CommonPasswordValidator.DEFAULT_PASSWORD_LIST_PATH:
password_list_path = self.DEFAULT_PASSWORD_LIST_PATH
try:
with gzip.open(password_list_path, "rt", encoding="utf-8") as f:
with gzip.open(password_list_path, 'rt', encoding='utf-8') as f:
self.passwords = {x.strip() for x in f}
except OSError:
with open(password_list_path) as f:
@@ -243,24 +181,23 @@ class CommonPasswordValidator:
if password.lower().strip() in self.passwords:
raise ValidationError(
_("This password is too common."),
code="password_too_common",
code='password_too_common',
)
def get_help_text(self):
return _("Your password cant be a commonly used password.")
return _('Your password cant be a commonly used password.')
class NumericPasswordValidator:
"""
Validate whether the password is alphanumeric.
"""
def validate(self, password, user=None):
if password.isdigit():
raise ValidationError(
_("This password is entirely numeric."),
code="password_entirely_numeric",
code='password_entirely_numeric',
)
def get_help_text(self):
return _("Your password cant be entirely numeric.")
return _('Your password cant be entirely numeric.')
@@ -1,4 +1,4 @@
from datetime import datetime
from datetime import datetime, time
from django.conf import settings
from django.utils.crypto import constant_time_compare, salted_hmac
@@ -10,21 +10,15 @@ class PasswordResetTokenGenerator:
Strategy object used to generate and check tokens for the password
reset mechanism.
"""
key_salt = "django.contrib.auth.tokens.PasswordResetTokenGenerator"
algorithm = None
_secret = None
secret = None
def __init__(self):
self.algorithm = self.algorithm or "sha256"
def _get_secret(self):
return self._secret or settings.SECRET_KEY
def _set_secret(self, secret):
self._secret = secret
secret = property(_get_secret, _set_secret)
self.secret = self.secret or settings.SECRET_KEY
# RemovedInDjango40Warning: when the deprecation ends, replace with:
# self.algorithm = self.algorithm or 'sha256'
self.algorithm = self.algorithm or settings.DEFAULT_HASHING_ALGORITHM
def make_token(self, user):
"""
@@ -42,6 +36,8 @@ class PasswordResetTokenGenerator:
# Parse the token
try:
ts_b36, _ = token.split("-")
# RemovedInDjango40Warning.
legacy_token = len(ts_b36) < 4
except ValueError:
return False
@@ -52,15 +48,28 @@ class PasswordResetTokenGenerator:
# Check that the timestamp/uid has not been tampered with
if not constant_time_compare(self._make_token_with_timestamp(user, ts), token):
return False
# RemovedInDjango40Warning: when the deprecation ends, replace
# with:
# return False
if not constant_time_compare(
self._make_token_with_timestamp(user, ts, legacy=True),
token,
):
return False
# RemovedInDjango40Warning: convert days to seconds and round to
# midnight (server time) for pre-Django 3.1 tokens.
now = self._now()
if legacy_token:
ts *= 24 * 60 * 60
ts += int((now - datetime.combine(now.date(), time.min)).total_seconds())
# Check the timestamp is within limit.
if (self._num_seconds(self._now()) - ts) > settings.PASSWORD_RESET_TIMEOUT:
if (self._num_seconds(now) - ts) > settings.PASSWORD_RESET_TIMEOUT:
return False
return True
def _make_token_with_timestamp(self, user, timestamp):
def _make_token_with_timestamp(self, user, timestamp, legacy=False):
# timestamp is number of seconds since 2001-1-1. Converted to base 36,
# this gives us a 6 digit string until about 2069.
ts_b36 = int_to_base36(timestamp)
@@ -68,10 +77,11 @@ class PasswordResetTokenGenerator:
self.key_salt,
self._make_hash_value(user, timestamp),
secret=self.secret,
algorithm=self.algorithm,
).hexdigest()[
::2
] # Limit to shorten the URL.
# RemovedInDjango40Warning: when the deprecation ends, remove the
# legacy argument and replace with:
# algorithm=self.algorithm,
algorithm='sha1' if legacy else self.algorithm,
).hexdigest()[::2] # Limit to shorten the URL.
return "%s-%s" % (ts_b36, hash_string)
def _make_hash_value(self, user, timestamp):
@@ -91,14 +101,10 @@ class PasswordResetTokenGenerator:
"""
# Truncate microseconds so that tokens are consistent even if the
# database doesn't support microseconds.
login_timestamp = (
""
if user.last_login is None
else user.last_login.replace(microsecond=0, tzinfo=None)
)
login_timestamp = '' if user.last_login is None else user.last_login.replace(microsecond=0, tzinfo=None)
email_field = user.get_email_field_name()
email = getattr(user, email_field, "") or ""
return f"{user.pk}{user.password}{login_timestamp}{timestamp}{email}"
email = getattr(user, email_field, '') or ''
return f'{user.pk}{user.password}{login_timestamp}{timestamp}{email}'
def _num_seconds(self, dt):
return int((dt - datetime(2001, 1, 1)).total_seconds())

Some files were not shown because too many files have changed in this diff Show More