mirror of
https://git.linux-kernel.at/oliver/ivatar.git
synced 2025-11-17 21:48:02 +00:00
First preparations for Django >= 4.x
- Slight reformatting in some parts; Non-functional changes - ugettext(_lazy) no longer available in Django > 4, changing to gettext(_lazy) - Since django-openid-auth doesn't work with Django > 4 yet, we need to pin this project to Django < 4 until that issue is solved
This commit is contained in:
@@ -6,7 +6,7 @@ Configuration overrides for settings.py
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.contrib.messages import constants as message_constants
|
from django.contrib.messages import constants as message_constants
|
||||||
from ivatar.settings import BASE_DIR
|
from ivatar.settings import BASE_DIR
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
'''
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
Classes for our ivatar.ivataraccount.forms
|
Classes for our ivatar.ivataraccount.forms
|
||||||
'''
|
"""
|
||||||
from urllib.parse import urlsplit, urlunsplit
|
from urllib.parse import urlsplit, urlunsplit
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from ipware import get_client_ip
|
from ipware import get_client_ip
|
||||||
|
|
||||||
@@ -20,93 +21,97 @@ MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT = 5
|
|||||||
|
|
||||||
|
|
||||||
class AddEmailForm(forms.Form):
|
class AddEmailForm(forms.Form):
|
||||||
'''
|
"""
|
||||||
Form to handle adding email addresses
|
Form to handle adding email addresses
|
||||||
'''
|
"""
|
||||||
|
|
||||||
email = forms.EmailField(
|
email = forms.EmailField(
|
||||||
label=_('Email'),
|
label=_("Email"),
|
||||||
min_length=MIN_LENGTH_EMAIL,
|
min_length=MIN_LENGTH_EMAIL,
|
||||||
max_length=MAX_LENGTH_EMAIL,
|
max_length=MAX_LENGTH_EMAIL,
|
||||||
)
|
)
|
||||||
|
|
||||||
def clean_email(self):
|
def clean_email(self):
|
||||||
'''
|
"""
|
||||||
Enforce lowercase email
|
Enforce lowercase email
|
||||||
'''
|
"""
|
||||||
# TODO: Domain restriction as in libravatar?
|
# TODO: Domain restriction as in libravatar?
|
||||||
return self.cleaned_data['email'].lower()
|
return self.cleaned_data["email"].lower()
|
||||||
|
|
||||||
def save(self, request):
|
def save(self, request):
|
||||||
'''
|
"""
|
||||||
Save the model, ensuring some safety
|
Save the model, ensuring some safety
|
||||||
'''
|
"""
|
||||||
user = request.user
|
user = request.user
|
||||||
# Enforce the maximum number of unconfirmed emails a user can have
|
# Enforce the maximum number of unconfirmed emails a user can have
|
||||||
num_unconfirmed = user.unconfirmedemail_set.count()
|
num_unconfirmed = user.unconfirmedemail_set.count()
|
||||||
|
|
||||||
max_num_unconfirmed_emails = getattr(
|
max_num_unconfirmed_emails = getattr(
|
||||||
settings,
|
settings, "MAX_NUM_UNCONFIRMED_EMAILS", MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT
|
||||||
'MAX_NUM_UNCONFIRMED_EMAILS',
|
)
|
||||||
MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT)
|
|
||||||
|
|
||||||
if num_unconfirmed >= max_num_unconfirmed_emails:
|
if num_unconfirmed >= max_num_unconfirmed_emails:
|
||||||
self.add_error(None, _('Too many unconfirmed mail addresses!'))
|
self.add_error(None, _("Too many unconfirmed mail addresses!"))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check whether or not a confirmation email has been
|
# Check whether or not a confirmation email has been
|
||||||
# sent by this user already
|
# sent by this user already
|
||||||
if UnconfirmedEmail.objects.filter( # pylint: disable=no-member
|
if UnconfirmedEmail.objects.filter( # pylint: disable=no-member
|
||||||
user=user, email=self.cleaned_data['email']).exists():
|
user=user, email=self.cleaned_data["email"]
|
||||||
self.add_error(
|
).exists():
|
||||||
'email',
|
self.add_error("email", _("Address already added, currently unconfirmed"))
|
||||||
_('Address already added, currently unconfirmed'))
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check whether or not the email is already confirmed (by someone)
|
# Check whether or not the email is already confirmed (by someone)
|
||||||
check_mail = ConfirmedEmail.objects.filter(
|
check_mail = ConfirmedEmail.objects.filter(email=self.cleaned_data["email"])
|
||||||
email=self.cleaned_data['email'])
|
|
||||||
if check_mail.exists():
|
if check_mail.exists():
|
||||||
msg = _('Address already confirmed (by someone else)')
|
msg = _("Address already confirmed (by someone else)")
|
||||||
if check_mail.first().user == request.user:
|
if check_mail.first().user == request.user:
|
||||||
msg = _('Address already confirmed (by you)')
|
msg = _("Address already confirmed (by you)")
|
||||||
self.add_error('email', msg)
|
self.add_error("email", msg)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
unconfirmed = UnconfirmedEmail()
|
unconfirmed = UnconfirmedEmail()
|
||||||
unconfirmed.email = self.cleaned_data['email']
|
unconfirmed.email = self.cleaned_data["email"]
|
||||||
unconfirmed.user = user
|
unconfirmed.user = user
|
||||||
unconfirmed.save()
|
unconfirmed.save()
|
||||||
unconfirmed.send_confirmation_mail(url=request.build_absolute_uri('/')[:-1])
|
unconfirmed.send_confirmation_mail(url=request.build_absolute_uri("/")[:-1])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class UploadPhotoForm(forms.Form):
|
class UploadPhotoForm(forms.Form):
|
||||||
'''
|
"""
|
||||||
Form handling photo upload
|
Form handling photo upload
|
||||||
'''
|
"""
|
||||||
|
|
||||||
photo = forms.FileField(
|
photo = forms.FileField(
|
||||||
label=_('Photo'),
|
label=_("Photo"),
|
||||||
error_messages={'required': _('You must choose an image to upload.')})
|
error_messages={"required": _("You must choose an image to upload.")},
|
||||||
|
)
|
||||||
not_porn = forms.BooleanField(
|
not_porn = forms.BooleanField(
|
||||||
label=_('suitable for all ages (i.e. no offensive content)'),
|
label=_("suitable for all ages (i.e. no offensive content)"),
|
||||||
required=True,
|
required=True,
|
||||||
error_messages={
|
error_messages={
|
||||||
'required':
|
"required": _(
|
||||||
_('We only host "G-rated" images and so this field must be checked.')
|
'We only host "G-rated" images and so this field must be checked.'
|
||||||
})
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
can_distribute = forms.BooleanField(
|
can_distribute = forms.BooleanField(
|
||||||
label=_('can be freely copied'),
|
label=_("can be freely copied"),
|
||||||
required=True,
|
required=True,
|
||||||
error_messages={
|
error_messages={
|
||||||
'required':
|
"required": _(
|
||||||
_('This field must be checked since we need to be able to distribute photos to third parties.')
|
"This field must be checked since we need to be able to distribute photos to third parties."
|
||||||
})
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def save(request, data):
|
def save(request, data):
|
||||||
'''
|
"""
|
||||||
Save the model and assign it to the current user
|
Save the model and assign it to the current user
|
||||||
'''
|
"""
|
||||||
# Link this file to the user's profile
|
# Link this file to the user's profile
|
||||||
photo = Photo()
|
photo = Photo()
|
||||||
photo.user = request.user
|
photo.user = request.user
|
||||||
@@ -119,47 +124,48 @@ class UploadPhotoForm(forms.Form):
|
|||||||
|
|
||||||
|
|
||||||
class AddOpenIDForm(forms.Form):
|
class AddOpenIDForm(forms.Form):
|
||||||
'''
|
"""
|
||||||
Form to handle adding OpenID
|
Form to handle adding OpenID
|
||||||
'''
|
"""
|
||||||
|
|
||||||
openid = forms.URLField(
|
openid = forms.URLField(
|
||||||
label=_('OpenID'),
|
label=_("OpenID"),
|
||||||
min_length=MIN_LENGTH_URL,
|
min_length=MIN_LENGTH_URL,
|
||||||
max_length=MAX_LENGTH_URL,
|
max_length=MAX_LENGTH_URL,
|
||||||
initial='http://'
|
initial="http://",
|
||||||
)
|
)
|
||||||
|
|
||||||
def clean_openid(self):
|
def clean_openid(self):
|
||||||
'''
|
"""
|
||||||
Enforce restrictions
|
Enforce restrictions
|
||||||
'''
|
"""
|
||||||
# Lowercase hostname port of the URL
|
# Lowercase hostname port of the URL
|
||||||
url = urlsplit(self.cleaned_data['openid'])
|
url = urlsplit(self.cleaned_data["openid"])
|
||||||
data = urlunsplit(
|
data = urlunsplit(
|
||||||
(url.scheme.lower(), url.netloc.lower(), url.path,
|
(url.scheme.lower(), url.netloc.lower(), url.path, url.query, url.fragment)
|
||||||
url.query, url.fragment))
|
)
|
||||||
|
|
||||||
# TODO: Domain restriction as in libravatar?
|
# TODO: Domain restriction as in libravatar?
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def save(self, user):
|
def save(self, user):
|
||||||
'''
|
"""
|
||||||
Save the model, ensuring some safety
|
Save the model, ensuring some safety
|
||||||
'''
|
"""
|
||||||
if ConfirmedOpenId.objects.filter( # pylint: disable=no-member
|
if ConfirmedOpenId.objects.filter( # pylint: disable=no-member
|
||||||
openid=self.cleaned_data['openid']).exists():
|
openid=self.cleaned_data["openid"]
|
||||||
self.add_error('openid', _('OpenID already added and confirmed!'))
|
).exists():
|
||||||
|
self.add_error("openid", _("OpenID already added and confirmed!"))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if UnconfirmedOpenId.objects.filter( # pylint: disable=no-member
|
if UnconfirmedOpenId.objects.filter( # pylint: disable=no-member
|
||||||
openid=self.cleaned_data['openid']).exists():
|
openid=self.cleaned_data["openid"]
|
||||||
self.add_error(
|
).exists():
|
||||||
'openid',
|
self.add_error("openid", _("OpenID already added, but not confirmed yet!"))
|
||||||
_('OpenID already added, but not confirmed yet!'))
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
unconfirmed = UnconfirmedOpenId()
|
unconfirmed = UnconfirmedOpenId()
|
||||||
unconfirmed.openid = self.cleaned_data['openid']
|
unconfirmed.openid = self.cleaned_data["openid"]
|
||||||
unconfirmed.user = user
|
unconfirmed.user = user
|
||||||
unconfirmed.save()
|
unconfirmed.save()
|
||||||
|
|
||||||
@@ -167,40 +173,50 @@ class AddOpenIDForm(forms.Form):
|
|||||||
|
|
||||||
|
|
||||||
class UpdatePreferenceForm(forms.ModelForm):
|
class UpdatePreferenceForm(forms.ModelForm):
|
||||||
'''
|
"""
|
||||||
Form for updating user preferences
|
Form for updating user preferences
|
||||||
'''
|
"""
|
||||||
|
|
||||||
class Meta: # pylint: disable=too-few-public-methods
|
class Meta: # pylint: disable=too-few-public-methods
|
||||||
'''
|
"""
|
||||||
Meta class for UpdatePreferenceForm
|
Meta class for UpdatePreferenceForm
|
||||||
'''
|
"""
|
||||||
|
|
||||||
model = UserPreference
|
model = UserPreference
|
||||||
fields = ['theme']
|
fields = ["theme"]
|
||||||
|
|
||||||
|
|
||||||
class UploadLibravatarExportForm(forms.Form):
|
class UploadLibravatarExportForm(forms.Form):
|
||||||
'''
|
"""
|
||||||
Form handling libravatar user export upload
|
Form handling libravatar user export upload
|
||||||
'''
|
"""
|
||||||
|
|
||||||
export_file = forms.FileField(
|
export_file = forms.FileField(
|
||||||
label=_('Export file'),
|
label=_("Export file"),
|
||||||
error_messages={'required': _('You must choose an export file to upload.')})
|
error_messages={"required": _("You must choose an export file to upload.")},
|
||||||
|
)
|
||||||
not_porn = forms.BooleanField(
|
not_porn = forms.BooleanField(
|
||||||
label=_('suitable for all ages (i.e. no offensive content)'),
|
label=_("suitable for all ages (i.e. no offensive content)"),
|
||||||
required=True,
|
required=True,
|
||||||
error_messages={
|
error_messages={
|
||||||
'required':
|
"required": _(
|
||||||
_('We only host "G-rated" images and so this field must be checked.')
|
'We only host "G-rated" images and so this field must be checked.'
|
||||||
})
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
can_distribute = forms.BooleanField(
|
can_distribute = forms.BooleanField(
|
||||||
label=_('can be freely copied'),
|
label=_("can be freely copied"),
|
||||||
required=True,
|
required=True,
|
||||||
error_messages={
|
error_messages={
|
||||||
'required':
|
"required": _(
|
||||||
_('This field must be checked since we need to be able to\
|
"This field must be checked since we need to be able to\
|
||||||
distribute photos to third parties.')
|
distribute photos to third parties."
|
||||||
})
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DeleteAccountForm(forms.Form):
|
class DeleteAccountForm(forms.Form):
|
||||||
password = forms.CharField(label=_('Password'), required=False, widget=forms.PasswordInput())
|
password = forms.CharField(
|
||||||
|
label=_("Password"), required=False, widget=forms.PasswordInput()
|
||||||
|
)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from django.db import models
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.urls import reverse_lazy, reverse
|
from django.urls import reverse_lazy, reverse
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from django.contrib.auth.views import LoginView
|
|||||||
from django.contrib.auth.views import (
|
from django.contrib.auth.views import (
|
||||||
PasswordResetView as PasswordResetViewOriginal,
|
PasswordResetView as PasswordResetViewOriginal,
|
||||||
)
|
)
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.http import HttpResponseRedirect, HttpResponse
|
from django.http import HttpResponseRedirect, HttpResponse
|
||||||
from django.urls import reverse_lazy, reverse
|
from django.urls import reverse_lazy, reverse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
'''
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
Classes for our ivatar.tools.forms
|
Classes for our ivatar.tools.forms
|
||||||
'''
|
"""
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.forms.utils import ErrorList
|
from django.forms.utils import ErrorList
|
||||||
|
|
||||||
@@ -12,45 +13,40 @@ from ivatar.settings import MIN_LENGTH_EMAIL, MAX_LENGTH_EMAIL
|
|||||||
|
|
||||||
|
|
||||||
class CheckDomainForm(forms.Form):
|
class CheckDomainForm(forms.Form):
|
||||||
'''
|
"""
|
||||||
Form handling domain check
|
Form handling domain check
|
||||||
'''
|
"""
|
||||||
|
|
||||||
domain = forms.CharField(
|
domain = forms.CharField(
|
||||||
label=_('Domain'),
|
label=_("Domain"),
|
||||||
required=True,
|
required=True,
|
||||||
error_messages={
|
error_messages={"required": _("Cannot check without a domain name.")},
|
||||||
'required':
|
|
||||||
_('Cannot check without a domain name.')
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CheckForm(forms.Form):
|
class CheckForm(forms.Form):
|
||||||
'''
|
"""
|
||||||
Form handling check
|
Form handling check
|
||||||
'''
|
"""
|
||||||
|
|
||||||
mail = forms.EmailField(
|
mail = forms.EmailField(
|
||||||
label=_('E-Mail'),
|
label=_("E-Mail"),
|
||||||
required=False,
|
required=False,
|
||||||
min_length=MIN_LENGTH_EMAIL,
|
min_length=MIN_LENGTH_EMAIL,
|
||||||
max_length=MAX_LENGTH_EMAIL,
|
max_length=MAX_LENGTH_EMAIL,
|
||||||
error_messages={
|
error_messages={"required": _("Cannot check without a domain name.")},
|
||||||
'required':
|
)
|
||||||
_('Cannot check without a domain name.')
|
|
||||||
})
|
|
||||||
|
|
||||||
openid = forms.CharField(
|
openid = forms.CharField(
|
||||||
label=_('OpenID'),
|
label=_("OpenID"),
|
||||||
required=False,
|
required=False,
|
||||||
min_length=MIN_LENGTH_URL,
|
min_length=MIN_LENGTH_URL,
|
||||||
max_length=MAX_LENGTH_URL,
|
max_length=MAX_LENGTH_URL,
|
||||||
error_messages={
|
error_messages={"required": _("Cannot check without an openid name.")},
|
||||||
'required':
|
)
|
||||||
_('Cannot check without an openid name.')
|
|
||||||
})
|
|
||||||
|
|
||||||
size = forms.IntegerField(
|
size = forms.IntegerField(
|
||||||
label=_('Size'),
|
label=_("Size"),
|
||||||
initial=80,
|
initial=80,
|
||||||
min_value=5,
|
min_value=5,
|
||||||
max_value=AVATAR_MAX_SIZE,
|
max_value=AVATAR_MAX_SIZE,
|
||||||
@@ -58,24 +54,24 @@ class CheckForm(forms.Form):
|
|||||||
)
|
)
|
||||||
|
|
||||||
default_opt = forms.ChoiceField(
|
default_opt = forms.ChoiceField(
|
||||||
label=_('Default'),
|
label=_("Default"),
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.RadioSelect,
|
widget=forms.RadioSelect,
|
||||||
choices=[
|
choices=[
|
||||||
('retro', _('Retro style (similar to GitHub)')),
|
("retro", _("Retro style (similar to GitHub)")),
|
||||||
('robohash', _('Roboter style')),
|
("robohash", _("Roboter style")),
|
||||||
('pagan', _('Retro adventure character')),
|
("pagan", _("Retro adventure character")),
|
||||||
('wavatar', _('Wavatar style')),
|
("wavatar", _("Wavatar style")),
|
||||||
('monsterid', _('Monster style')),
|
("monsterid", _("Monster style")),
|
||||||
('identicon', _('Identicon style')),
|
("identicon", _("Identicon style")),
|
||||||
('mm', _('Mystery man')),
|
("mm", _("Mystery man")),
|
||||||
('mmng', _('Mystery man NextGen')),
|
("mmng", _("Mystery man NextGen")),
|
||||||
('none', _('None')),
|
("none", _("None")),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
default_url = forms.URLField(
|
default_url = forms.URLField(
|
||||||
label=_('Default URL'),
|
label=_("Default URL"),
|
||||||
min_length=1,
|
min_length=1,
|
||||||
max_length=MAX_LENGTH_URL,
|
max_length=MAX_LENGTH_URL,
|
||||||
required=False,
|
required=False,
|
||||||
@@ -83,28 +79,28 @@ class CheckForm(forms.Form):
|
|||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.cleaned_data = super().clean()
|
self.cleaned_data = super().clean()
|
||||||
mail = self.cleaned_data.get('mail')
|
mail = self.cleaned_data.get("mail")
|
||||||
openid = self.cleaned_data.get('openid')
|
openid = self.cleaned_data.get("openid")
|
||||||
default_url = self.cleaned_data.get('default_url')
|
default_url = self.cleaned_data.get("default_url")
|
||||||
default_opt = self.cleaned_data.get('default_opt')
|
default_opt = self.cleaned_data.get("default_opt")
|
||||||
if default_url and default_opt and default_opt != 'none':
|
if default_url and default_opt and default_opt != "none":
|
||||||
if not 'default_url' in self._errors:
|
if "default_url" not in self._errors:
|
||||||
self._errors['default_url'] = ErrorList()
|
self._errors["default_url"] = ErrorList()
|
||||||
if not 'default_opt' in self._errors:
|
if "default_opt" not in self._errors:
|
||||||
self._errors['default_opt'] = ErrorList()
|
self._errors["default_opt"] = ErrorList()
|
||||||
|
|
||||||
errstring = _('Only default URL OR default keyword may be specified')
|
errstring = _("Only default URL OR default keyword may be specified")
|
||||||
self._errors['default_url'].append(errstring)
|
self._errors["default_url"].append(errstring)
|
||||||
self._errors['default_opt'].append(errstring)
|
self._errors["default_opt"].append(errstring)
|
||||||
if not mail and not openid:
|
if not mail and not openid:
|
||||||
raise ValidationError(_('Either OpenID or mail must be specified'))
|
raise ValidationError(_("Either OpenID or mail must be specified"))
|
||||||
return self.cleaned_data
|
return self.cleaned_data
|
||||||
|
|
||||||
def clean_openid(self):
|
def clean_openid(self):
|
||||||
data = self.cleaned_data['openid']
|
data = self.cleaned_data["openid"]
|
||||||
return data.lower()
|
return data.lower()
|
||||||
|
|
||||||
def clean_mail(self):
|
def clean_mail(self):
|
||||||
data = self.cleaned_data['mail']
|
data = self.cleaned_data["mail"]
|
||||||
print(data)
|
print(data)
|
||||||
return data.lower()
|
return data.lower()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from django.http import HttpResponse, HttpResponseRedirect
|
|||||||
from django.http import HttpResponseNotFound, JsonResponse
|
from django.http import HttpResponseNotFound, JsonResponse
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.core.cache import cache, caches
|
from django.core.cache import cache, caches
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
autopep8
|
autopep8
|
||||||
bcrypt
|
bcrypt
|
||||||
defusedxml
|
defusedxml
|
||||||
Django
|
Django < 4.0
|
||||||
django-anymail[mailgun]
|
django-anymail[mailgun]
|
||||||
django-auth-ldap
|
django-auth-ldap
|
||||||
django-bootstrap4
|
django-bootstrap4
|
||||||
|
|||||||
Reference in New Issue
Block a user