mirror of
https://github.com/cookiecutter/cookiecutter-django.git
synced 2024-11-10 19:57:09 +03:00
Merge branch 'master' into drf-auth-token
This commit is contained in:
commit
729cd2adab
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
|
@ -1,7 +1,7 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: danielroygreenfeld
|
||||
github: pydanny
|
||||
patreon: roygreenfeld
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
|
|
|
@ -15,10 +15,6 @@ matrix:
|
|||
include:
|
||||
- name: Test results
|
||||
script: tox -e py37
|
||||
- name: Run flake8 on result
|
||||
script: tox -e flake8
|
||||
- name: Run black on result
|
||||
script: tox -e black
|
||||
- name: Black template
|
||||
script: tox -e black-template
|
||||
- name: Basic Docker
|
||||
|
|
|
@ -49,6 +49,7 @@ Listed in alphabetical order.
|
|||
Adam Dobrawy `@ad-m`_
|
||||
Adam Steele `@adammsteele`_
|
||||
Agam Dua
|
||||
Agustín Scaramuzza `@scaramagus`_ @scaramagus
|
||||
Alberto Sanchez `@alb3rto`_
|
||||
Alex Tsai `@caffodian`_
|
||||
Alvaro [Andor] `@andor-pierdelacabeza`_
|
||||
|
@ -118,6 +119,7 @@ Listed in alphabetical order.
|
|||
Garry Cairns `@garry-cairns`_
|
||||
Garry Polley `@garrypolley`_
|
||||
Gilbishkosma `@Gilbishkosma`_
|
||||
Guilherme Guy `@guilherme1guy`_
|
||||
Hamish Durkin `@durkode`_
|
||||
Hana Quadara `@hanaquadara`_
|
||||
Harry Moreno `@morenoh149`_ @morenoh149
|
||||
|
@ -275,6 +277,7 @@ Listed in alphabetical order.
|
|||
.. _@dhepper: https://github.com/dhepper
|
||||
.. _@dot2dotseurat: https://github.com/dot2dotseurat
|
||||
.. _@dsclementsen: https://github.com/dsclementsen
|
||||
.. _@guilherme1guy: https://github.com/guilherme1guy
|
||||
.. _@durkode: https://github.com/durkode
|
||||
.. _@Egregors: https://github.com/Egregors
|
||||
.. _@epileptic-fish: https://gihub.com/epileptic-fish
|
||||
|
@ -350,6 +353,7 @@ Listed in alphabetical order.
|
|||
.. _@rolep: https://github.com/rolep
|
||||
.. _@romanosipenko: https://github.com/romanosipenko
|
||||
.. _@saschalalala: https://github.com/saschalalala
|
||||
.. _@scaramagus: https://github.com/scaramagus
|
||||
.. _@shireenrao: https://github.com/shireenrao
|
||||
.. _@show0k: https://github.com/show0k
|
||||
.. _@shultz: https://github.com/shultz
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
[pytest]
|
||||
addopts = -x --tb=short
|
||||
addopts = -v --tb=short
|
||||
python_paths = .
|
||||
norecursedirs = .tox .git */migrations/* */static/* docs venv */{{cookiecutter.project_slug}}/*
|
||||
markers =
|
||||
flake8: Run flake8 on all possible template combinations
|
||||
black: Run black on all possible template combinations
|
||||
|
|
|
@ -6,12 +6,12 @@ binaryornot==0.4.4
|
|||
# ------------------------------------------------------------------------------
|
||||
black==19.10b0
|
||||
flake8==3.7.9
|
||||
flake8-isort==2.8.0
|
||||
|
||||
# Testing
|
||||
# ------------------------------------------------------------------------------
|
||||
tox==3.14.5
|
||||
pytest==5.3.5
|
||||
pytest_cases==1.12.2
|
||||
pytest==5.4.1
|
||||
pytest-cookies==0.5.1
|
||||
pytest-xdist==1.31.0
|
||||
pytest-instafail==0.4.1.post0
|
||||
pyyaml==5.3
|
||||
|
|
|
@ -3,7 +3,6 @@ import re
|
|||
|
||||
import pytest
|
||||
from cookiecutter.exceptions import FailedHookException
|
||||
from pytest_cases import fixture_plus
|
||||
import sh
|
||||
import yaml
|
||||
from binaryornot.check import is_binary
|
||||
|
@ -26,49 +25,62 @@ def context():
|
|||
}
|
||||
|
||||
|
||||
@fixture_plus
|
||||
@pytest.mark.parametrize("windows", ["y", "n"], ids=lambda yn: f"win:{yn}")
|
||||
@pytest.mark.parametrize("use_docker", ["y", "n"], ids=lambda yn: f"docker:{yn}")
|
||||
@pytest.mark.parametrize("use_celery", ["y", "n"], ids=lambda yn: f"celery:{yn}")
|
||||
@pytest.mark.parametrize("use_mailhog", ["y", "n"], ids=lambda yn: f"mailhog:{yn}")
|
||||
@pytest.mark.parametrize("use_sentry", ["y", "n"], ids=lambda yn: f"sentry:{yn}")
|
||||
@pytest.mark.parametrize("use_compressor", ["y", "n"], ids=lambda yn: f"cmpr:{yn}")
|
||||
@pytest.mark.parametrize("use_drf", ["y", "n"], ids=lambda yn: f"drf:{yn}")
|
||||
@pytest.mark.parametrize(
|
||||
"use_whitenoise,cloud_provider",
|
||||
[
|
||||
("y", "AWS"),
|
||||
("y", "GCP"),
|
||||
("y", "None"),
|
||||
("n", "AWS"),
|
||||
("n", "GCP"),
|
||||
# no whitenoise + no cloud provider is not supported
|
||||
],
|
||||
ids=lambda id: f"wnoise:{id[0]}-cloud:{id[1]}",
|
||||
)
|
||||
def context_combination(
|
||||
windows,
|
||||
use_docker,
|
||||
use_celery,
|
||||
use_mailhog,
|
||||
use_sentry,
|
||||
use_compressor,
|
||||
use_whitenoise,
|
||||
use_drf,
|
||||
cloud_provider,
|
||||
):
|
||||
"""Fixture that parametrize the function where it's used."""
|
||||
return {
|
||||
"windows": windows,
|
||||
"use_docker": use_docker,
|
||||
"use_compressor": use_compressor,
|
||||
"use_celery": use_celery,
|
||||
"use_mailhog": use_mailhog,
|
||||
"use_sentry": use_sentry,
|
||||
"use_whitenoise": use_whitenoise,
|
||||
"use_drf": use_drf,
|
||||
"cloud_provider": cloud_provider,
|
||||
}
|
||||
SUPPORTED_COMBINATIONS = [
|
||||
{"open_source_license": "MIT"},
|
||||
{"open_source_license": "BSD"},
|
||||
{"open_source_license": "GPLv3"},
|
||||
{"open_source_license": "Apache Software License 2.0"},
|
||||
{"open_source_license": "Not open source"},
|
||||
{"windows": "y"},
|
||||
{"windows": "n"},
|
||||
{"use_pycharm": "y"},
|
||||
{"use_pycharm": "n"},
|
||||
{"use_docker": "y"},
|
||||
{"use_docker": "n"},
|
||||
{"postgresql_version": "11.3"},
|
||||
{"postgresql_version": "10.8"},
|
||||
{"postgresql_version": "9.6"},
|
||||
{"postgresql_version": "9.5"},
|
||||
{"postgresql_version": "9.4"},
|
||||
{"cloud_provider": "AWS", "use_whitenoise": "y"},
|
||||
{"cloud_provider": "AWS", "use_whitenoise": "n"},
|
||||
{"cloud_provider": "GCP", "use_whitenoise": "y"},
|
||||
{"cloud_provider": "GCP", "use_whitenoise": "n"},
|
||||
{"cloud_provider": "None", "use_whitenoise": "y"},
|
||||
# Note: cloud_provider=None AND use_whitenoise=n is not supported
|
||||
{"use_drf": "y"},
|
||||
{"use_drf": "n"},
|
||||
{"js_task_runner": "None"},
|
||||
{"js_task_runner": "Gulp"},
|
||||
{"custom_bootstrap_compilation": "y"},
|
||||
{"custom_bootstrap_compilation": "n"},
|
||||
{"use_compressor": "y"},
|
||||
{"use_compressor": "n"},
|
||||
{"use_celery": "y"},
|
||||
{"use_celery": "n"},
|
||||
{"use_mailhog": "y"},
|
||||
{"use_mailhog": "n"},
|
||||
{"use_sentry": "y"},
|
||||
{"use_sentry": "n"},
|
||||
{"use_whitenoise": "y"},
|
||||
{"use_whitenoise": "n"},
|
||||
{"use_heroku": "y"},
|
||||
{"use_heroku": "n"},
|
||||
{"ci_tool": "None"},
|
||||
{"ci_tool": "Travis"},
|
||||
{"ci_tool": "Gitlab"},
|
||||
{"keep_local_envs_in_vcs": "y"},
|
||||
{"keep_local_envs_in_vcs": "n"},
|
||||
{"debug": "y"},
|
||||
{"debug": "n"},
|
||||
]
|
||||
|
||||
UNSUPPORTED_COMBINATIONS = [{"cloud_provider": "None", "use_whitenoise": "n"}]
|
||||
|
||||
|
||||
def _fixture_id(ctx):
|
||||
"""Helper to get a user friendly test name from the parametrized context."""
|
||||
return "-".join(f"{key}:{value}" for key, value in ctx.items())
|
||||
|
||||
|
||||
def build_files_list(root_dir):
|
||||
|
@ -81,9 +93,7 @@ def build_files_list(root_dir):
|
|||
|
||||
|
||||
def check_paths(paths):
|
||||
"""Method to check all paths have correct substitutions,
|
||||
used by other tests cases
|
||||
"""
|
||||
"""Method to check all paths have correct substitutions."""
|
||||
# Assert that no match is found in any of the files
|
||||
for path in paths:
|
||||
if is_binary(path):
|
||||
|
@ -95,13 +105,10 @@ def check_paths(paths):
|
|||
assert match is None, msg.format(path)
|
||||
|
||||
|
||||
def test_project_generation(cookies, context, context_combination):
|
||||
"""
|
||||
Test that project is generated and fully rendered.
|
||||
|
||||
This is parametrized for each combination from ``context_combination`` fixture
|
||||
"""
|
||||
result = cookies.bake(extra_context={**context, **context_combination})
|
||||
@pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id)
|
||||
def test_project_generation(cookies, context, context_override):
|
||||
"""Test that project is generated and fully rendered."""
|
||||
result = cookies.bake(extra_context={**context, **context_override})
|
||||
assert result.exit_code == 0
|
||||
assert result.exception is None
|
||||
assert result.project.basename == context["project_slug"]
|
||||
|
@ -112,34 +119,26 @@ def test_project_generation(cookies, context, context_combination):
|
|||
check_paths(paths)
|
||||
|
||||
|
||||
@pytest.mark.flake8
|
||||
def test_flake8_passes(cookies, context_combination):
|
||||
"""
|
||||
Generated project should pass flake8.
|
||||
|
||||
This is parametrized for each combination from ``context_combination`` fixture
|
||||
"""
|
||||
result = cookies.bake(extra_context=context_combination)
|
||||
@pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id)
|
||||
def test_flake8_passes(cookies, context_override):
|
||||
"""Generated project should pass flake8."""
|
||||
result = cookies.bake(extra_context=context_override)
|
||||
|
||||
try:
|
||||
sh.flake8(str(result.project))
|
||||
except sh.ErrorReturnCode as e:
|
||||
pytest.fail(e)
|
||||
pytest.fail(e.stdout.decode())
|
||||
|
||||
|
||||
@pytest.mark.black
|
||||
def test_black_passes(cookies, context_combination):
|
||||
"""
|
||||
Generated project should pass black.
|
||||
|
||||
This is parametrized for each combination from ``context_combination`` fixture
|
||||
"""
|
||||
result = cookies.bake(extra_context=context_combination)
|
||||
@pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id)
|
||||
def test_black_passes(cookies, context_override):
|
||||
"""Generated project should pass black."""
|
||||
result = cookies.bake(extra_context=context_override)
|
||||
|
||||
try:
|
||||
sh.black("--check", "--diff", "--exclude", "migrations", f"{result.project}/")
|
||||
except sh.ErrorReturnCode as e:
|
||||
pytest.fail(e)
|
||||
pytest.fail(e.stdout.decode())
|
||||
|
||||
|
||||
def test_travis_invokes_pytest(cookies, context):
|
||||
|
@ -187,9 +186,10 @@ def test_invalid_slug(cookies, context, slug):
|
|||
assert isinstance(result.exception, FailedHookException)
|
||||
|
||||
|
||||
def test_no_whitenoise_and_no_cloud_provider(cookies, context):
|
||||
"""It should not generate project if neither whitenoise or cloud provider are set"""
|
||||
context.update({"use_whitenoise": "n", "cloud_provider": "None"})
|
||||
@pytest.mark.parametrize("invalid_context", UNSUPPORTED_COMBINATIONS)
|
||||
def test_error_if_incompatible(cookies, context, invalid_context):
|
||||
"""It should not generate project an incompatible combination is selected."""
|
||||
context.update(invalid_context)
|
||||
result = cookies.bake(extra_context=context)
|
||||
|
||||
assert result.exit_code != 0
|
||||
|
|
12
tox.ini
12
tox.ini
|
@ -1,18 +1,10 @@
|
|||
[tox]
|
||||
skipsdist = true
|
||||
envlist = py37,flake8,black,black-template
|
||||
envlist = py37,black-template
|
||||
|
||||
[testenv]
|
||||
deps = -rrequirements.txt
|
||||
commands = pytest -m "not flake8" -m "not black" {posargs:./tests}
|
||||
|
||||
[testenv:flake8]
|
||||
deps = -rrequirements.txt
|
||||
commands = pytest -m flake8 {posargs:./tests}
|
||||
|
||||
[testenv:black]
|
||||
deps = -rrequirements.txt
|
||||
commands = pytest -m black {posargs:./tests}
|
||||
commands = pytest {posargs:./tests}
|
||||
|
||||
[testenv:black-template]
|
||||
deps = black
|
||||
|
|
|
@ -13,8 +13,8 @@ indent_style = space
|
|||
indent_size = 4
|
||||
|
||||
[*.py]
|
||||
line_length = 120
|
||||
known_first_party = {{ cookiecutter.project_slug }}
|
||||
line_length = 88
|
||||
known_first_party = {{cookiecutter.project_slug}},config
|
||||
multi_line_output = 3
|
||||
default_section = THIRDPARTY
|
||||
recursive = true
|
||||
|
|
|
@ -6,6 +6,7 @@ variables:
|
|||
POSTGRES_USER: '{{ cookiecutter.project_slug }}'
|
||||
POSTGRES_PASSWORD: ''
|
||||
POSTGRES_DB: 'test_{{ cookiecutter.project_slug }}'
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
|
||||
flake8:
|
||||
stage: lint
|
||||
|
|
|
@ -2,4 +2,5 @@ release: python manage.py migrate
|
|||
web: gunicorn config.wsgi:application
|
||||
{% if cookiecutter.use_celery == "y" -%}
|
||||
worker: celery worker --app=config.celery_app --loglevel=info
|
||||
beat: celery beat --app=config.celery_app --loglevel=info
|
||||
{%- endif %}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
FROM python:3.7-slim-buster
|
||||
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
ENV PYTHONDONTWRITEBYTECODE 1
|
||||
|
||||
RUN apt-get update \
|
||||
# dependencies for building Python packages
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from rest_framework.routers import DefaultRouter, SimpleRouter
|
||||
from django.conf import settings
|
||||
from rest_framework.routers import DefaultRouter, SimpleRouter
|
||||
|
||||
from {{ cookiecutter.project_slug }}.users.api.views import UserViewSet
|
||||
|
||||
if settings.DEBUG:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
|
||||
from celery import Celery
|
||||
|
||||
# set the default Django settings module for the 'celery' program.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"""isort:skip_file"""
|
||||
{% if cookiecutter.use_sentry == 'y' -%}
|
||||
import logging
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
from django.conf import settings
|
||||
from django.urls import include, path
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.views.generic import TemplateView
|
||||
from django.urls import include, path
|
||||
from django.views import defaults as default_views
|
||||
{% if cookiecutter.use_drf == 'y' -%}
|
||||
from django.views.generic import TemplateView
|
||||
{%- if cookiecutter.use_drf == 'y' %}
|
||||
from rest_framework.authtoken.views import obtain_auth_token
|
||||
{%- endif %}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ whitenoise==5.0.1 # https://github.com/evansd/whitenoise
|
|||
{%- endif %}
|
||||
redis==3.4.1 # https://github.com/andymccurdy/redis-py
|
||||
{%- if cookiecutter.use_celery == "y" %}
|
||||
celery==4.4.0 # pyup: < 5.0 # https://github.com/celery/celery
|
||||
celery==4.4.1 # pyup: < 5.0 # https://github.com/celery/celery
|
||||
django-celery-beat==2.0.0 # https://github.com/celery/django-celery-beat
|
||||
{%- if cookiecutter.use_docker == 'y' %}
|
||||
flower==0.9.3 # https://github.com/mher/flower
|
||||
|
@ -19,7 +19,7 @@ flower==0.9.3 # https://github.com/mher/flower
|
|||
|
||||
# Django
|
||||
# ------------------------------------------------------------------------------
|
||||
django==2.2.10 # pyup: < 3.0 # https://www.djangoproject.com/
|
||||
django==2.2.11 # pyup: < 3.0 # https://www.djangoproject.com/
|
||||
django-environ==0.4.5 # https://github.com/joke2k/django-environ
|
||||
django-model-utils==4.0.0 # https://github.com/jazzband/django-model-utils
|
||||
django-allauth==0.41.0 # https://github.com/pennersr/django-allauth
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
-r ./base.txt
|
||||
|
||||
Werkzeug==1.0.0 # https://github.com/pallets/werkzeug
|
||||
ipdb==0.13.1 # https://github.com/gotcha/ipdb
|
||||
Sphinx==2.4.3 # https://github.com/sphinx-doc/sphinx
|
||||
ipdb==0.13.2 # https://github.com/gotcha/ipdb
|
||||
Sphinx==2.4.4 # https://github.com/sphinx-doc/sphinx
|
||||
{%- if cookiecutter.use_docker == 'y' %}
|
||||
psycopg2==2.8.4 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
|
||||
{%- else %}
|
||||
|
@ -11,21 +11,22 @@ psycopg2-binary==2.8.4 # https://github.com/psycopg/psycopg2
|
|||
|
||||
# Testing
|
||||
# ------------------------------------------------------------------------------
|
||||
mypy==0.761 # https://github.com/python/mypy
|
||||
django-stubs==1.4.0 # https://github.com/typeddjango/django-stubs
|
||||
mypy==0.770 # https://github.com/python/mypy
|
||||
django-stubs==1.5.0 # https://github.com/typeddjango/django-stubs
|
||||
pytest==5.3.5 # https://github.com/pytest-dev/pytest
|
||||
pytest-sugar==0.9.2 # https://github.com/Frozenball/pytest-sugar
|
||||
|
||||
# Code quality
|
||||
# ------------------------------------------------------------------------------
|
||||
flake8==3.7.9 # https://github.com/PyCQA/flake8
|
||||
flake8-isort==2.8.0 # https://github.com/gforcada/flake8-isort
|
||||
coverage==5.0.3 # https://github.com/nedbat/coveragepy
|
||||
black==19.10b0 # https://github.com/ambv/black
|
||||
pylint-django==2.0.14 # https://github.com/PyCQA/pylint-django
|
||||
{%- if cookiecutter.use_celery == 'y' %}
|
||||
pylint-celery==0.3 # https://github.com/PyCQA/pylint-celery
|
||||
{%- endif %}
|
||||
pre-commit==2.1.1 # https://github.com/pre-commit/pre-commit
|
||||
pre-commit==2.2.0 # https://github.com/pre-commit/pre-commit
|
||||
|
||||
# Django
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
gunicorn==20.0.4 # https://github.com/benoitc/gunicorn
|
||||
psycopg2==2.8.4 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
|
||||
{%- if cookiecutter.use_whitenoise == 'n' %}
|
||||
Collectfast==2.0.1 # https://github.com/antonagestam/collectfast
|
||||
Collectfast==2.1.0 # https://github.com/antonagestam/collectfast
|
||||
{%- endif %}
|
||||
{%- if cookiecutter.use_sentry == "y" %}
|
||||
sentry-sdk==0.14.2 # https://github.com/getsentry/sentry-python
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from {{ cookiecutter.project_slug }}.users.models import User
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.mixins import RetrieveModelMixin, ListModelMixin, UpdateModelMixin
|
||||
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, UpdateModelMixin
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from django.contrib.auth import get_user_model, forms
|
||||
from django.contrib.auth import forms, get_user_model
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import pytest
|
||||
from celery.result import EagerResult
|
||||
|
||||
|
||||
from {{ cookiecutter.project_slug }}.users.tasks import get_users_count
|
||||
from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import pytest
|
||||
from django.urls import reverse, resolve
|
||||
from django.urls import resolve, reverse
|
||||
|
||||
from {{ cookiecutter.project_slug }}.users.models import User
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from django.urls import path
|
||||
|
||||
from {{ cookiecutter.project_slug }}.users.views import (
|
||||
user_detail_view,
|
||||
user_redirect_view,
|
||||
user_update_view,
|
||||
user_detail_view,
|
||||
)
|
||||
|
||||
app_name = "users"
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from django.contrib import messages
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.urls import reverse
|
||||
from django.views.generic import DetailView, RedirectView, UpdateView
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import DetailView, RedirectView, UpdateView
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user