测试gitnore
This commit is contained in:
@@ -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 didn’t match."),
|
||||
'password_mismatch': _('The two password fields didn’t 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 "
|
||||
"user’s password, but you can change the password using "
|
||||
'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>.'
|
||||
),
|
||||
)
|
||||
|
||||
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 didn’t match."),
|
||||
'password_mismatch': _('The two password fields didn’t 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 didn’t match."),
|
||||
'password_mismatch': _('The two password fields didn’t 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):
|
||||
|
||||
Binary file not shown.
@@ -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 "الاسم"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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 "назва"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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 didn’t 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 user’s "
|
||||
"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 can’t 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 can’t 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 can’t 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 "Въведете нова парола"
|
||||
|
||||
Binary file not shown.
@@ -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 didn’t 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 user’s "
|
||||
"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 can’t 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 can’t 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 can’t be entirely numeric."
|
||||
msgid "Your password can't be entirely numeric."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
|
||||
Binary file not shown.
@@ -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"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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ě"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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 didn’t 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 user’s "
|
||||
"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 can’t 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 can’t 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 can’t 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 didn’t 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 user’s "
|
||||
"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 can’t 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 can’t 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 can’t 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 ""
|
||||
|
||||
Binary file not shown.
@@ -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 didn’t match."
|
||||
msgstr "The two password fields didn’t 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 user’s "
|
||||
"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 user’s "
|
||||
"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 can’t be too similar to your other personal information."
|
||||
msgstr "Your password can’t 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 can’t be a commonly used password."
|
||||
msgstr "Your password can’t 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 can’t be entirely numeric."
|
||||
msgstr "Your password can’t 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 ""
|
||||
|
||||
Binary file not shown.
@@ -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"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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 "نام"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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 didn’t 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 user’s "
|
||||
"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 can’t 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 can’t 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 can’t 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 ""
|
||||
|
||||
Binary file not shown.
@@ -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"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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 "ವೈಯುಕ್ತಿಕ ಮಾಹಿತಿ"
|
||||
|
||||
Binary file not shown.
@@ -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 "이름"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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 "аты"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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 didn’t 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 user’s "
|
||||
"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 can’t 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 can’t 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 can’t 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"
|
||||
Binary file not shown.
@@ -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 didn’t 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 user’s "
|
||||
"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 can’t 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 can’t 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 can’t 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 ""
|
||||
|
||||
Binary file not shown.
@@ -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"
|
||||
|
||||
Binary file not shown.
@@ -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 didn’t 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 user’s "
|
||||
"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 can’t 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 can’t 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 can’t be entirely numeric."
|
||||
msgid "Your password can't be entirely numeric."
|
||||
msgstr "Vaše heslo nemôže pozostávať iba z číslic."
|
||||
|
||||
#, python-format
|
||||
|
||||
Binary file not shown.
@@ -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 "име"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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 didn’t 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 user’s "
|
||||
"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 can’t 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 can’t 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 can’t be entirely numeric."
|
||||
msgid "Your password can't be entirely numeric."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
|
||||
Binary file not shown.
@@ -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ı"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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 "ім'я"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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 didn’t 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 user’s "
|
||||
"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 can’t 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 can’t 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 can’t 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"
|
||||
|
||||
Binary file not shown.
@@ -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()
|
||||
|
||||
+69
-121
@@ -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
-4
@@ -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
-6
@@ -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),
|
||||
),
|
||||
]
|
||||
|
||||
+6
-11
@@ -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
-6
@@ -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),
|
||||
),
|
||||
]
|
||||
|
||||
+2
-2
@@ -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 = [
|
||||
|
||||
+6
-9
@@ -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',
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
+6
-9
@@ -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
-6
@@ -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
-4
@@ -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'),
|
||||
),
|
||||
]
|
||||
|
||||
+12
-19
@@ -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
-6
@@ -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 can’t be too similar to your other personal information."
|
||||
)
|
||||
return _('Your password can’t 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 can’t be a commonly used password.")
|
||||
return _('Your password can’t 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 can’t be entirely numeric.")
|
||||
return _('Your password can’t 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
Reference in New Issue
Block a user