forked from mirrors/bookwyrm
Merge pull request #1389 from bookwyrm-social/email-blocking
Email blocking
This commit is contained in:
commit
9a53808eeb
29 changed files with 361 additions and 26 deletions
|
@ -299,6 +299,12 @@ class ReportForm(CustomForm):
|
|||
fields = ["user", "reporter", "statuses", "note"]
|
||||
|
||||
|
||||
class EmailBlocklistForm(CustomForm):
|
||||
class Meta:
|
||||
model = models.EmailBlocklist
|
||||
fields = ["domain"]
|
||||
|
||||
|
||||
class ServerForm(CustomForm):
|
||||
class Meta:
|
||||
model = models.FederatedServer
|
||||
|
|
32
bookwyrm/migrations/0090_emailblocklist.py
Normal file
32
bookwyrm/migrations/0090_emailblocklist.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
# Generated by Django 3.2.4 on 2021-09-08 22:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookwyrm", "0089_user_show_suggested_users"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="EmailBlocklist",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("created_date", models.DateTimeField(auto_now_add=True)),
|
||||
("domain", models.CharField(max_length=255, unique=True)),
|
||||
],
|
||||
options={
|
||||
"ordering": ("-created_date",),
|
||||
},
|
||||
),
|
||||
]
|
|
@ -0,0 +1,13 @@
|
|||
# Generated by Django 3.2.4 on 2021-09-09 00:43
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookwyrm", "0090_auto_20210908_2346"),
|
||||
("bookwyrm", "0090_emailblocklist"),
|
||||
]
|
||||
|
||||
operations = []
|
|
@ -24,7 +24,9 @@ from .federated_server import FederatedServer
|
|||
|
||||
from .import_job import ImportJob, ImportItem
|
||||
|
||||
from .site import SiteSettings, SiteInvite, PasswordReset, InviteRequest
|
||||
from .site import SiteSettings, SiteInvite
|
||||
from .site import PasswordReset, InviteRequest
|
||||
from .site import EmailBlocklist
|
||||
from .announcement import Announcement
|
||||
|
||||
cls_members = inspect.getmembers(sys.modules[__name__], inspect.isclass)
|
||||
|
|
|
@ -123,6 +123,23 @@ class PasswordReset(models.Model):
|
|||
return "https://{}/password-reset/{}".format(DOMAIN, self.code)
|
||||
|
||||
|
||||
class EmailBlocklist(models.Model):
|
||||
"""blocked email addresses"""
|
||||
|
||||
created_date = models.DateTimeField(auto_now_add=True)
|
||||
domain = models.CharField(max_length=255, unique=True)
|
||||
|
||||
class Meta:
|
||||
"""default sorting"""
|
||||
|
||||
ordering = ("-created_date",)
|
||||
|
||||
@property
|
||||
def users(self):
|
||||
"""find the users associated with this address"""
|
||||
return User.objects.filter(email__endswith=f"@{self.domain}")
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@receiver(models.signals.post_save, sender=SiteSettings)
|
||||
def preview_image(instance, *args, **kwargs):
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'settings/admin_layout.html' %}
|
||||
{% extends 'settings/layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load humanize %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'settings/admin_layout.html' %}
|
||||
{% extends 'settings/layout.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'settings/admin_layout.html' %}
|
||||
{% extends 'settings/layout.html' %}
|
||||
{% load i18n %}{% load humanize %}
|
||||
{% block title %}{% trans "Announcement" %} - {{ announcement.preview }}{% endblock %}
|
||||
|
||||
|
|
|
@ -13,21 +13,21 @@
|
|||
{% csrf_token %}
|
||||
<input type="hidden" name="user" value="{{ request.user.id }}">
|
||||
<p>
|
||||
<label class="label" for="id_preview">Preview:</label>
|
||||
<label class="label" for="id_preview">{% trans "Preview:" %}</label>
|
||||
{{ form.preview }}
|
||||
{% for error in form.preview.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
</p>
|
||||
<p>
|
||||
<label class="label" for="id_content">Content:</label>
|
||||
<label class="label" for="id_content">{% trans "Content:" %}</label>
|
||||
{{ form.content }}
|
||||
{% for error in form.content.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
</p>
|
||||
<p>
|
||||
<label class="label" for="id_event_date">Event date:</label>
|
||||
<label class="label" for="id_event_date">{% trans "Event date:" %}</label>
|
||||
<input type="date" name="event_date" value="{{ form.event_date.value|date:'Y-m-d' }}" class="input" id="id_event_date">
|
||||
{% for error in form.event_date.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
|
@ -37,7 +37,7 @@
|
|||
<div class="columns">
|
||||
<div class="column">
|
||||
<p>
|
||||
<label class="label" for="id_start_date">Start date:</label>
|
||||
<label class="label" for="id_start_date">{% trans "Start date:" %}</label>
|
||||
<input type="date" name="start_date" class="input" value="{{ form.start_date.value|date:'Y-m-d' }}" id="id_start_date">
|
||||
{% for error in form.start_date.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
|
@ -46,7 +46,7 @@
|
|||
</div>
|
||||
<div class="column">
|
||||
<p>
|
||||
<label class="label" for="id_end_date">End date:</label>
|
||||
<label class="label" for="id_end_date">{% trans "End date:" %}</label>
|
||||
<input type="date" name="end_date" class="input" id="id_end_date" value="{{ form.end_date.value|date:'Y-m-d' }}">
|
||||
{% for error in form.end_date.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
|
@ -55,7 +55,7 @@
|
|||
</div>
|
||||
<div class="column is-narrow">
|
||||
<p>
|
||||
<label class="label" for="id_active">Active:</label>
|
||||
<label class="label" for="id_active">{% trans "Active:" %}</label>
|
||||
{{ form.active }}
|
||||
{% for error in form.active.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'settings/admin_layout.html' %}
|
||||
{% extends 'settings/layout.html' %}
|
||||
{% load i18n %}{% load humanize %}
|
||||
{% block title %}{% trans "Announcements" %}{% endblock %}
|
||||
|
||||
|
|
30
bookwyrm/templates/settings/domain_form.html
Normal file
30
bookwyrm/templates/settings/domain_form.html
Normal file
|
@ -0,0 +1,30 @@
|
|||
{% extends 'components/inline_form.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block header %}
|
||||
{% trans "Add domain" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block form %}
|
||||
<form name="add-domain" method="post" action="{% url 'settings-email-blocks' %}">
|
||||
{% csrf_token %}
|
||||
<label class="label" for="id_event_date">{% trans "Domain:" %}</label>
|
||||
<div class="field has-addons">
|
||||
<div class="control">
|
||||
<div class="button is-disabled">@</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
{{ form.domain }}
|
||||
</div>
|
||||
</div>
|
||||
{% for error in form.domain.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-primary">{% trans "Add" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'settings/admin_layout.html' %}
|
||||
{% extends 'settings/layout.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Add instance" %}{% endblock %}
|
||||
|
||||
|
|
62
bookwyrm/templates/settings/email_blocklist.html
Normal file
62
bookwyrm/templates/settings/email_blocklist.html
Normal file
|
@ -0,0 +1,62 @@
|
|||
{% extends 'settings/layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block title %}{% trans "Email Blocklist" %}{% endblock %}
|
||||
|
||||
{% block header %}{% trans "Email Blocklist" %}{% endblock %}
|
||||
|
||||
{% block edit-button %}
|
||||
{% trans "Add domain" as button_text %}
|
||||
{% include 'snippets/toggle/open_button.html' with controls_text="add_domain" icon_with_text="plus" text=button_text focus="add_domain_header" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
{% include 'settings/domain_form.html' with controls_text="add_domain" class="block" %}
|
||||
|
||||
<p class="notification block">
|
||||
{% trans "When someone tries to register with an email from this domain, no account will be created. The registration process will appear to have worked." %}
|
||||
</p>
|
||||
|
||||
<table class="table is-striped is-fullwidth">
|
||||
<tr>
|
||||
{% url 'settings-federation' as url %}
|
||||
<th>
|
||||
{% trans "Domain" as text %}
|
||||
{% include 'snippets/table-sort-header.html' with field="server_name" sort=sort text=text %}
|
||||
</th>
|
||||
<th></th>
|
||||
<th>
|
||||
{% trans "Options" %}
|
||||
</th>
|
||||
</tr>
|
||||
{% for domain in domains %}
|
||||
<tr>
|
||||
<td>{{ domain.domain }}</td>
|
||||
<td>
|
||||
<a href="{% url 'settings-users' %}?email=@{{ domain.domain }}">
|
||||
{% with user_count=domain.users.count %}
|
||||
{% blocktrans trimmed count conter=user_count with display_count=user_count|intcomma %}
|
||||
{{ display_count }} user
|
||||
{% plural %}
|
||||
{{ display_count }} users
|
||||
{% endblocktrans %}
|
||||
{% endwith %}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<form name="remove-{{ domain.id }}" action="{% url 'settings-email-blocks-delete' domain.id %}" method="post">
|
||||
{% csrf_token %}
|
||||
{% trans "Delete" as button_text %}
|
||||
<button class="button" type="submit">
|
||||
<span class="icon icon-x" title="{{ button_text }}" aria-hidden="true"></span>
|
||||
<span class="is-hidden-mobile">{{ button_text }}</span>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'settings/admin_layout.html' %}
|
||||
{% extends 'settings/layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load markdown %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'settings/admin_layout.html' %}
|
||||
{% extends 'settings/layout.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Federated Instances" %}{% endblock %}
|
||||
|
||||
|
|
|
@ -32,12 +32,6 @@
|
|||
{% url 'settings-invites' as alt_url %}
|
||||
<a href="{{ url }}"{% if url in request.path or request.path in alt_url %} class="is-active" aria-selected="true"{% endif %}>{% trans "Invites" %}</a>
|
||||
</li>
|
||||
{% if perms.bookwyrm.moderate_user %}
|
||||
<li>
|
||||
{% url 'settings-reports' as url %}
|
||||
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Reports" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if perms.bookwyrm.control_federation %}
|
||||
<li>
|
||||
{% url 'settings-federation' as url %}
|
||||
|
@ -46,6 +40,19 @@
|
|||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if perms.bookwyrm.moderate_user %}
|
||||
<h2 class="menu-label">{% trans "Moderation" %}</h2>
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
{% url 'settings-reports' as url %}
|
||||
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Reports" %}</a>
|
||||
</li>
|
||||
<li>
|
||||
{% url 'settings-email-blocks' as url %}
|
||||
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Email Blocklist" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if perms.bookwyrm.edit_instance_settings %}
|
||||
<h2 class="menu-label">{% trans "Instance Settings" %}</h2>
|
||||
<ul class="menu-list">
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'settings/admin_layout.html' %}
|
||||
{% extends 'settings/layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load humanize %}
|
||||
{% block header %}{% trans "Invite Requests" %}{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'settings/admin_layout.html' %}
|
||||
{% extends 'settings/layout.html' %}
|
||||
{% load i18n %}
|
||||
{% block header %}{% trans "Invites" %}{% endblock %}
|
||||
{% load humanize %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'settings/admin_layout.html' %}
|
||||
{% extends 'settings/layout.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Add instance" %}{% endblock %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'settings/admin_layout.html' %}
|
||||
{% extends 'settings/layout.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Site Settings" %}{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'settings/admin_layout.html' %}
|
||||
{% extends 'settings/layout.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{{ user.username }}{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'settings/admin_layout.html' %}
|
||||
{% extends 'settings/layout.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Users" %}{% endblock %}
|
||||
|
||||
|
|
73
bookwyrm/tests/views/test_email_blocks.py
Normal file
73
bookwyrm/tests/views/test_email_blocks.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
""" test for app action functionality """
|
||||
from unittest.mock import patch
|
||||
from django.template.response import TemplateResponse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from bookwyrm import models, views
|
||||
|
||||
|
||||
class EmailBlocklistViews(TestCase):
|
||||
"""every response to a get request, html or json"""
|
||||
|
||||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||
"bookwyrm.activitystreams.populate_stream_task.delay"
|
||||
):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.mouse",
|
||||
"password",
|
||||
local=True,
|
||||
localname="mouse",
|
||||
)
|
||||
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_blocklist_page_get(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.EmailBlocklist.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
request.user.is_superuser = True
|
||||
|
||||
result = view(request)
|
||||
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
result.render()
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_blocklist_page_post(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.EmailBlocklist.as_view()
|
||||
request = self.factory.post("", {"domain": "gmail.com"})
|
||||
request.user = self.local_user
|
||||
request.user.is_superuser = True
|
||||
|
||||
result = view(request)
|
||||
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
result.render()
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
self.assertTrue(
|
||||
models.EmailBlocklist.objects.filter(domain="gmail.com").exists()
|
||||
)
|
||||
|
||||
def test_blocklist_page_delete(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
domain = models.EmailBlocklist.objects.create(domain="gmail.com")
|
||||
|
||||
view = views.EmailBlocklist.as_view()
|
||||
request = self.factory.post("")
|
||||
request.user = self.local_user
|
||||
request.user.is_superuser = True
|
||||
|
||||
result = view(request, domain_id=domain.id)
|
||||
self.assertEqual(result.status_code, 302)
|
||||
|
||||
self.assertFalse(
|
||||
models.EmailBlocklist.objects.filter(domain="gmail.com").exists()
|
||||
)
|
|
@ -153,6 +153,30 @@ class RegisterViews(TestCase):
|
|||
with self.assertRaises(PermissionDenied):
|
||||
view(request)
|
||||
|
||||
def test_register_blocked_domain(self, *_):
|
||||
"""you can't register with a blocked domain"""
|
||||
view = views.Register.as_view()
|
||||
models.EmailBlocklist.objects.create(domain="gmail.com")
|
||||
|
||||
# one that fails
|
||||
request = self.factory.post(
|
||||
"register/",
|
||||
{"localname": "nutria ", "password": "mouseword", "email": "aa@gmail.com"},
|
||||
)
|
||||
result = view(request)
|
||||
self.assertEqual(result.status_code, 302)
|
||||
self.assertFalse(models.User.objects.filter(email="aa@gmail.com").exists())
|
||||
|
||||
# one that succeeds
|
||||
request = self.factory.post(
|
||||
"register/",
|
||||
{"localname": "nutria ", "password": "mouseword", "email": "aa@bleep.com"},
|
||||
)
|
||||
with patch("bookwyrm.views.register.login"):
|
||||
result = view(request)
|
||||
self.assertEqual(result.status_code, 302)
|
||||
self.assertTrue(models.User.objects.filter(email="aa@bleep.com").exists())
|
||||
|
||||
def test_register_invite(self, *_):
|
||||
"""you can't just register"""
|
||||
view = views.Register.as_view()
|
||||
|
|
|
@ -141,6 +141,16 @@ urlpatterns = [
|
|||
r"^invite-request/?$", views.InviteRequest.as_view(), name="invite-request"
|
||||
),
|
||||
re_path(r"^invite/(?P<code>[A-Za-z0-9]+)/?$", views.Invite.as_view()),
|
||||
re_path(
|
||||
r"^settings/email-blocklist/?$",
|
||||
views.EmailBlocklist.as_view(),
|
||||
name="settings-email-blocks",
|
||||
),
|
||||
re_path(
|
||||
r"^settings/email-blocks/(?P<domain_id>\d+)/delete/?$",
|
||||
views.EmailBlocklist.as_view(),
|
||||
name="settings-email-blocks-delete",
|
||||
),
|
||||
# moderation
|
||||
re_path(r"^settings/reports/?$", views.Reports.as_view(), name="settings-reports"),
|
||||
re_path(
|
||||
|
|
|
@ -8,6 +8,7 @@ from .directory import Directory
|
|||
from .discover import Discover
|
||||
from .edit_user import EditUser, DeleteUser
|
||||
from .editions import Editions, switch_edition
|
||||
from .email_blocklist import EmailBlocklist
|
||||
from .federation import Federation, FederatedServer
|
||||
from .federation import AddFederatedServer, ImportServerBlocklist
|
||||
from .federation import block_server, unblock_server
|
||||
|
|
49
bookwyrm/views/email_blocklist.py
Normal file
49
bookwyrm/views/email_blocklist.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
""" moderation via flagged posts and users """
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
|
||||
from bookwyrm import forms, models
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
@method_decorator(
|
||||
permission_required("bookwyrm.moderate_user", raise_exception=True),
|
||||
name="dispatch",
|
||||
)
|
||||
class EmailBlocklist(View):
|
||||
"""Block users by email address"""
|
||||
|
||||
def get(self, request):
|
||||
"""view and compose blocks"""
|
||||
data = {
|
||||
"domains": models.EmailBlocklist.objects.order_by("-created_date").all(),
|
||||
"form": forms.EmailBlocklistForm(),
|
||||
}
|
||||
return TemplateResponse(request, "settings/email_blocklist.html", data)
|
||||
|
||||
def post(self, request, domain_id=None):
|
||||
"""create a new domain block"""
|
||||
if domain_id:
|
||||
return self.delete(request, domain_id)
|
||||
|
||||
form = forms.EmailBlocklistForm(request.POST)
|
||||
data = {
|
||||
"domains": models.EmailBlocklist.objects.order_by("-created_date").all(),
|
||||
"form": form,
|
||||
}
|
||||
if not form.is_valid():
|
||||
return TemplateResponse(request, "settings/email_blocklist.html", data)
|
||||
form.save()
|
||||
|
||||
data["form"] = forms.EmailBlocklistForm()
|
||||
return TemplateResponse(request, "settings/email_blocklist.html", data)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def delete(self, request, domain_id):
|
||||
"""remove a domain block"""
|
||||
domain = get_object_or_404(models.EmailBlocklist, id=domain_id)
|
||||
domain.delete()
|
||||
return redirect("settings-email-blocks")
|
|
@ -42,6 +42,12 @@ class Register(View):
|
|||
email = form.data["email"]
|
||||
password = form.data["password"]
|
||||
|
||||
# make sure the email isn't blocked as spam
|
||||
email_domain = email.split("@")[-1]
|
||||
if models.EmailBlocklist.objects.filter(domain=email_domain).exists():
|
||||
# treat this like a successful registration, but don't do anything
|
||||
return redirect("confirm-email")
|
||||
|
||||
# check localname and email uniqueness
|
||||
if models.User.objects.filter(localname=localname).first():
|
||||
form.errors["localname"] = ["User with this username already exists"]
|
||||
|
|
|
@ -33,6 +33,9 @@ class UserAdminList(View):
|
|||
scope = request.GET.get("scope")
|
||||
if scope and scope == "local":
|
||||
filters["local"] = True
|
||||
email = request.GET.get("email")
|
||||
if email:
|
||||
filters["email__endswith"] = email
|
||||
|
||||
users = models.User.objects.filter(**filters)
|
||||
|
||||
|
|
Loading…
Reference in a new issue