mirror of
https://github.com/leaders-of-digital-9-task/backend.git
synced 2025-07-23 22:29:46 +03:00
renamed polygon to roi
This commit is contained in:
parent
a40bc6ad5b
commit
3061d1cfac
|
@ -1,10 +1,10 @@
|
|||
from dicom.api.views import (
|
||||
CreateCircleApi,
|
||||
CreatePolygonApi,
|
||||
CreateroiApi,
|
||||
ListCreateDicomApi,
|
||||
RetrieveUpdateDeleteCircleApi,
|
||||
RetrieveUpdateDeleteDicomApi,
|
||||
RetrieveUpdateDeletePolygonApi,
|
||||
RetrieveUpdateDeleteroiApi,
|
||||
SmartFileUploadApi,
|
||||
)
|
||||
from django.urls import include, path
|
||||
|
@ -34,9 +34,9 @@ urlpatterns = [
|
|||
name="get_update_delete_dicom",
|
||||
),
|
||||
path(
|
||||
"<str:slug>/polygon",
|
||||
CreatePolygonApi.as_view(),
|
||||
name="create_polygon",
|
||||
"<str:slug>/Roi",
|
||||
CreateroiApi.as_view(),
|
||||
name="create_roi",
|
||||
),
|
||||
path(
|
||||
"<str:slug>/circle",
|
||||
|
@ -51,9 +51,9 @@ urlpatterns = [
|
|||
include(
|
||||
[
|
||||
path(
|
||||
"polygon/<int:id>",
|
||||
RetrieveUpdateDeletePolygonApi.as_view(),
|
||||
name="get_update_delete_polygon",
|
||||
"Roi/<int:id>",
|
||||
RetrieveUpdateDeleteroiApi.as_view(),
|
||||
name="get_update_delete_roi",
|
||||
),
|
||||
path(
|
||||
"circle/<int:id>",
|
||||
|
|
|
@ -66,14 +66,7 @@ DJANGO_APPS = [
|
|||
"django.forms",
|
||||
]
|
||||
THIRD_PARTY_APPS = [
|
||||
"crispy_forms",
|
||||
"crispy_bootstrap5",
|
||||
"allauth",
|
||||
"allauth.account",
|
||||
"allauth.socialaccount",
|
||||
"django_celery_beat",
|
||||
"rest_framework",
|
||||
"rest_framework.authtoken",
|
||||
"corsheaders",
|
||||
"drf_spectacular",
|
||||
]
|
||||
|
@ -189,7 +182,6 @@ TEMPLATES = [
|
|||
"django.template.context_processors.static",
|
||||
"django.template.context_processors.tz",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"image_markuper.users.context_processors.allauth_settings",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
@ -284,23 +276,6 @@ CELERY_TASK_TIME_LIMIT = 5 * 60
|
|||
CELERY_TASK_SOFT_TIME_LIMIT = 60
|
||||
# https://docs.celeryq.dev/en/stable/userguide/configuration.html#beat-scheduler
|
||||
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
|
||||
# django-allauth
|
||||
# ------------------------------------------------------------------------------
|
||||
ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True)
|
||||
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
||||
ACCOUNT_AUTHENTICATION_METHOD = "username"
|
||||
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
||||
ACCOUNT_EMAIL_REQUIRED = True
|
||||
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
||||
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
|
||||
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
||||
ACCOUNT_ADAPTER = "image_markuper.users.adapters.AccountAdapter"
|
||||
# https://django-allauth.readthedocs.io/en/latest/forms.html
|
||||
ACCOUNT_FORMS = {"signup": "image_markuper.users.forms.UserSignupForm"}
|
||||
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
||||
SOCIALACCOUNT_ADAPTER = "image_markuper.users.adapters.SocialAccountAdapter"
|
||||
# https://django-allauth.readthedocs.io/en/latest/forms.html
|
||||
SOCIALACCOUNT_FORMS = {"signup": "image_markuper.users.forms.UserSocialSignupForm"}
|
||||
|
||||
# django-rest-framework
|
||||
# -------------------------------------------------------------------------------
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from dicom.models import Circle, Dicom, Polygon
|
||||
from dicom.models import Circle, Dicom, Roi
|
||||
from django.contrib import admin
|
||||
|
||||
admin.site.register(Dicom)
|
||||
admin.site.register(Circle)
|
||||
admin.site.register(Polygon)
|
||||
admin.site.register(Roi)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from dicom.models import Circle, Coordinate, Dicom, Polygon
|
||||
from dicom.models import Circle, Coordinate, Dicom, Roi
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
from rest_framework import serializers
|
||||
from rest_framework.generics import get_object_or_404
|
||||
|
@ -34,7 +34,7 @@ class ListDicomSerializer(serializers.ModelSerializer):
|
|||
|
||||
|
||||
class BaseShapeSerializer(serializers.Serializer):
|
||||
type = serializers.ChoiceField(choices=["circle", "polygon"])
|
||||
type = serializers.ChoiceField(choices=["circle", "Roi"])
|
||||
image_number = serializers.IntegerField()
|
||||
coordinates = CoordinateSerializer(many=True)
|
||||
|
||||
|
@ -52,11 +52,11 @@ class DicomSerializer(serializers.ModelSerializer):
|
|||
fields = ["file", "uploaded", "pathology_type", "shapes"]
|
||||
|
||||
|
||||
class PolygonSerializer(serializers.ModelSerializer):
|
||||
class RoiSerializer(serializers.ModelSerializer):
|
||||
coordinates = CoordinateSerializer(many=True)
|
||||
|
||||
class Meta:
|
||||
model = Polygon
|
||||
model = Roi
|
||||
fields = ["id", "image_number", "coordinates"]
|
||||
extra_kwargs = {"id": {"read_only": True}}
|
||||
|
||||
|
@ -66,12 +66,12 @@ class PolygonSerializer(serializers.ModelSerializer):
|
|||
dicom = get_object_or_404(
|
||||
Dicom, slug=self.context["request"].parser_context["kwargs"]["slug"]
|
||||
)
|
||||
polygon = Polygon.objects.create(
|
||||
roi = Roi.objects.create(
|
||||
dicom=dicom, image_number=validated_data["image_number"]
|
||||
)
|
||||
|
||||
create_coordinate(validated_data["coordinates"], polygon)
|
||||
return polygon
|
||||
create_coordinate(validated_data["coordinates"], roi)
|
||||
return roi
|
||||
|
||||
def update(self, obj: Circle, validated_data):
|
||||
Coordinate.objects.filter(shape=obj).delete()
|
||||
|
@ -114,5 +114,5 @@ class CircleSerializer(serializers.ModelSerializer):
|
|||
return obj
|
||||
|
||||
|
||||
class SmartFileUploadSerializer(serializers.ModelSerializer):
|
||||
class SmartFileUploadSerializer(serializers.Serializer):
|
||||
file = serializers.FileField()
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
from drf_spectacular.utils import extend_schema
|
||||
from rest_framework import generics, status
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.generics import get_object_or_404
|
||||
from rest_framework.generics import GenericAPIView, get_object_or_404
|
||||
from rest_framework.parsers import FormParser, MultiPartParser
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from ..models import Circle, Dicom, Polygon
|
||||
from ..models import Circle, Dicom, Roi
|
||||
from ..services import process_files
|
||||
from .serializers import (
|
||||
CircleSerializer,
|
||||
DicomSerializer,
|
||||
ListDicomSerializer,
|
||||
PolygonSerializer,
|
||||
RoiSerializer,
|
||||
SmartFileUploadSerializer,
|
||||
)
|
||||
|
||||
|
||||
|
@ -34,21 +34,19 @@ class RetrieveUpdateDeleteDicomApi(generics.RetrieveUpdateDestroyAPIView):
|
|||
lookup_field = "slug"
|
||||
|
||||
|
||||
class CreatePolygonApi(generics.CreateAPIView):
|
||||
serializer_class = PolygonSerializer
|
||||
class CreateroiApi(generics.CreateAPIView):
|
||||
serializer_class = RoiSerializer
|
||||
|
||||
|
||||
class CreateCircleApi(generics.CreateAPIView):
|
||||
serializer_class = CircleSerializer
|
||||
|
||||
|
||||
class RetrieveUpdateDeletePolygonApi(generics.RetrieveUpdateDestroyAPIView):
|
||||
serializer_class = PolygonSerializer
|
||||
class RetrieveUpdateDeleteroiApi(generics.RetrieveUpdateDestroyAPIView):
|
||||
serializer_class = RoiSerializer
|
||||
|
||||
def get_object(self):
|
||||
return get_object_or_404(
|
||||
Polygon, id=self.request.parser_context["kwargs"]["id"]
|
||||
)
|
||||
return get_object_or_404(Roi, id=self.request.parser_context["kwargs"]["id"])
|
||||
|
||||
@extend_schema(description="Note: coordinated are dropped on update")
|
||||
def put(self, request, *args, **kwargs):
|
||||
|
@ -74,9 +72,11 @@ class RetrieveUpdateDeleteCircleApi(generics.RetrieveUpdateDestroyAPIView):
|
|||
return self.update(request, *args, **kwargs)
|
||||
|
||||
|
||||
class SmartFileUploadApi(APIView):
|
||||
class SmartFileUploadApi(GenericAPIView):
|
||||
parser_classes = [MultiPartParser, FormParser]
|
||||
serializer_class = SmartFileUploadSerializer
|
||||
|
||||
@extend_schema(responses={201: DicomSerializer(many=True)})
|
||||
def post(self, request):
|
||||
if "file" not in request.data:
|
||||
raise ValidationError("no files")
|
||||
|
@ -85,3 +85,7 @@ class SmartFileUploadApi(APIView):
|
|||
DicomSerializer(d_list.files.all(), many=True).data,
|
||||
status=status.HTTP_201_CREATED,
|
||||
)
|
||||
|
||||
|
||||
class UpdateDicomLayerApi(GenericAPIView):
|
||||
serializer_class = SmartFileUploadSerializer
|
||||
|
|
|
@ -1,28 +1,10 @@
|
|||
# Generated by Django 4.0.8 on 2022-10-26 15:01
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import utils.files
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Dicom',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('slug', models.SlugField()),
|
||||
('file', models.FileField(upload_to=utils.files.media_upload_path)),
|
||||
('uploaded', models.DateTimeField(auto_now_add=True)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='files', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
||||
operations = []
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
# flake8: noqa
|
||||
from .base import Dicom, ListOfDicom
|
||||
from .blocks import BaseShape, Circle, Coordinate, Polygon
|
||||
from .blocks import BaseShape, Circle, Coordinate, Roi
|
||||
|
|
|
@ -47,14 +47,14 @@ class Circle(BaseShape):
|
|||
return f"circle on {self.dicom.file.name}"
|
||||
|
||||
|
||||
class Polygon(BaseShape):
|
||||
class Roi(BaseShape):
|
||||
def serialize_self(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"type": "polygon",
|
||||
"type": "Roi",
|
||||
"image_number": self.image_number,
|
||||
"coordinates": self.coordinates,
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
return f"polygon on {self.dicom.file.name}"
|
||||
return f"Roi on {self.dicom.file.name}"
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
from typing import Any
|
||||
|
||||
from allauth.account.adapter import DefaultAccountAdapter
|
||||
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
|
||||
from django.conf import settings
|
||||
from django.http import HttpRequest
|
||||
|
||||
|
||||
class AccountAdapter(DefaultAccountAdapter):
|
||||
def is_open_for_signup(self, request: HttpRequest):
|
||||
return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
|
||||
|
||||
|
||||
class SocialAccountAdapter(DefaultSocialAccountAdapter):
|
||||
def is_open_for_signup(self, request: HttpRequest, sociallogin: Any):
|
||||
return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
|
|
@ -1,33 +1,7 @@
|
|||
from django.contrib import admin
|
||||
from django.contrib.auth import admin as auth_admin
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from image_markuper.users.forms import UserAdminChangeForm, UserAdminCreationForm
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
@admin.register(User)
|
||||
class UserAdmin(auth_admin.UserAdmin):
|
||||
|
||||
form = UserAdminChangeForm
|
||||
add_form = UserAdminCreationForm
|
||||
fieldsets = (
|
||||
(None, {"fields": ("username", "password")}),
|
||||
(
|
||||
_("Permissions"),
|
||||
{
|
||||
"fields": (
|
||||
"is_active",
|
||||
"is_staff",
|
||||
"is_superuser",
|
||||
"groups",
|
||||
"user_permissions",
|
||||
),
|
||||
},
|
||||
),
|
||||
(_("Important dates"), {"fields": ("last_login", "date_joined")}),
|
||||
)
|
||||
list_display = ["username", "is_superuser"]
|
||||
search_fields = ["username"]
|
||||
admin.site.register(User)
|
||||
|
|
|
@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
|
||||
|
||||
class UsersConfig(AppConfig):
|
||||
name = "image_markuper.users"
|
||||
name = "users"
|
||||
verbose_name = _("Users")
|
||||
|
||||
def ready(self):
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
from django.conf import settings
|
||||
|
||||
|
||||
def allauth_settings(request):
|
||||
"""Expose some settings from django-allauth in templates."""
|
||||
return {
|
||||
"ACCOUNT_ALLOW_REGISTRATION": settings.ACCOUNT_ALLOW_REGISTRATION,
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
from allauth.account.forms import SignupForm
|
||||
from allauth.socialaccount.forms import SignupForm as SocialSignupForm
|
||||
from django.contrib.auth import forms as admin_forms
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class UserAdminChangeForm(admin_forms.UserChangeForm):
|
||||
class Meta(admin_forms.UserChangeForm.Meta):
|
||||
model = User
|
||||
|
||||
|
||||
class UserAdminCreationForm(admin_forms.UserCreationForm):
|
||||
"""
|
||||
Form for User Creation in the Admin Area.
|
||||
To change user signup, see UserSignupForm and UserSocialSignupForm.
|
||||
"""
|
||||
|
||||
class Meta(admin_forms.UserCreationForm.Meta):
|
||||
model = User
|
||||
|
||||
error_messages = {
|
||||
"username": {"unique": _("This username has already been taken.")}
|
||||
}
|
||||
|
||||
|
||||
class UserSignupForm(SignupForm):
|
||||
"""
|
||||
Form that will be rendered on a user sign up section/screen.
|
||||
Default fields will be added automatically.
|
||||
Check UserSocialSignupForm for accounts created from social.
|
||||
"""
|
||||
|
||||
|
||||
class UserSocialSignupForm(SocialSignupForm):
|
||||
"""
|
||||
Renders the form when user has signed up using social accounts.
|
||||
Default fields will be added automatically.
|
||||
See UserSignupForm otherwise.
|
||||
"""
|
|
@ -1,124 +1,10 @@
|
|||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("auth", "0012_alter_user_first_name_max_length"),
|
||||
]
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="User",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("password", models.CharField(max_length=128, verbose_name="password")),
|
||||
(
|
||||
"last_login",
|
||||
models.DateTimeField(
|
||||
blank=True, null=True, 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(
|
||||
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=[
|
||||
django.contrib.auth.validators.UnicodeUsernameValidator()
|
||||
],
|
||||
verbose_name="username",
|
||||
),
|
||||
),
|
||||
(
|
||||
"email",
|
||||
models.EmailField(
|
||||
blank=True, max_length=254, verbose_name="email address"
|
||||
),
|
||||
),
|
||||
(
|
||||
"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,
|
||||
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
|
||||
verbose_name="active",
|
||||
),
|
||||
),
|
||||
(
|
||||
"date_joined",
|
||||
models.DateTimeField(
|
||||
default=django.utils.timezone.now, verbose_name="date joined"
|
||||
),
|
||||
),
|
||||
(
|
||||
"name",
|
||||
models.CharField(
|
||||
blank=True, max_length=255, verbose_name="Name of User"
|
||||
),
|
||||
),
|
||||
(
|
||||
"groups",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="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",
|
||||
to="auth.Group",
|
||||
verbose_name="groups",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user_permissions",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="Specific permissions for this user.",
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
to="auth.Permission",
|
||||
verbose_name="user permissions",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "user",
|
||||
"verbose_name_plural": "users",
|
||||
"abstract": False,
|
||||
},
|
||||
managers=[
|
||||
("objects", django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
]
|
||||
operations = []
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
|
||||
from config import celery_app
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
@celery_app.task()
|
||||
def get_users_count():
|
||||
"""A pointless Celery task to demonstrate usage."""
|
||||
return User.objects.count()
|
|
@ -1,33 +0,0 @@
|
|||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from factory import Faker, post_generation
|
||||
from factory.django import DjangoModelFactory
|
||||
|
||||
|
||||
class UserFactory(DjangoModelFactory):
|
||||
|
||||
username = Faker("user_name")
|
||||
email = Faker("email")
|
||||
name = Faker("name")
|
||||
|
||||
@post_generation
|
||||
def password(self, create: bool, extracted: Sequence[Any], **kwargs):
|
||||
password = (
|
||||
extracted
|
||||
if extracted
|
||||
else Faker(
|
||||
"password",
|
||||
length=42,
|
||||
special_chars=True,
|
||||
digits=True,
|
||||
upper_case=True,
|
||||
lower_case=True,
|
||||
).evaluate(None, None, extra={"locale": None})
|
||||
)
|
||||
self.set_password(password)
|
||||
|
||||
class Meta:
|
||||
model = get_user_model()
|
||||
django_get_or_create = ["username"]
|
|
@ -1,37 +0,0 @@
|
|||
from django.urls import reverse
|
||||
|
||||
from image_markuper.users.models import User
|
||||
|
||||
|
||||
class TestUserAdmin:
|
||||
def test_changelist(self, admin_client):
|
||||
url = reverse("admin:users_user_changelist")
|
||||
response = admin_client.get(url)
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_search(self, admin_client):
|
||||
url = reverse("admin:users_user_changelist")
|
||||
response = admin_client.get(url, data={"q": "test"})
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_add(self, admin_client):
|
||||
url = reverse("admin:users_user_add")
|
||||
response = admin_client.get(url)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = admin_client.post(
|
||||
url,
|
||||
data={
|
||||
"username": "test",
|
||||
"password1": "My_R@ndom-P@ssw0rd",
|
||||
"password2": "My_R@ndom-P@ssw0rd",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 302
|
||||
assert User.objects.filter(username="test").exists()
|
||||
|
||||
def test_view_user(self, admin_client):
|
||||
user = User.objects.get(username="admin")
|
||||
url = reverse("admin:users_user_change", kwargs={"object_id": user.pk})
|
||||
response = admin_client.get(url)
|
||||
assert response.status_code == 200
|
|
@ -1,21 +0,0 @@
|
|||
from django.urls import resolve, reverse
|
||||
|
||||
from image_markuper.users.models import User
|
||||
|
||||
|
||||
def test_user_detail(user: User):
|
||||
assert (
|
||||
reverse("api:user-detail", kwargs={"username": user.username})
|
||||
== f"/api/users/{user.username}/"
|
||||
)
|
||||
assert resolve(f"/api/users/{user.username}/").view_name == "api:user-detail"
|
||||
|
||||
|
||||
def test_user_list():
|
||||
assert reverse("api:user-list") == "/api/users/"
|
||||
assert resolve("/api/users/").view_name == "api:user-list"
|
||||
|
||||
|
||||
def test_user_me():
|
||||
assert reverse("api:user-me") == "/api/users/me/"
|
||||
assert resolve("/api/users/me/").view_name == "api:user-me"
|
|
@ -1,30 +0,0 @@
|
|||
from django.test import RequestFactory
|
||||
|
||||
from image_markuper.users.api.views import UserViewSet
|
||||
from image_markuper.users.models import User
|
||||
|
||||
|
||||
class TestUserViewSet:
|
||||
def test_get_queryset(self, user: User, rf: RequestFactory):
|
||||
view = UserViewSet()
|
||||
request = rf.get("/fake-url/")
|
||||
request.user = user
|
||||
|
||||
view.request = request
|
||||
|
||||
assert user in view.get_queryset()
|
||||
|
||||
def test_me(self, user: User, rf: RequestFactory):
|
||||
view = UserViewSet()
|
||||
request = rf.get("/fake-url/")
|
||||
request.user = user
|
||||
|
||||
view.request = request
|
||||
|
||||
response = view.me(request)
|
||||
|
||||
assert response.data == {
|
||||
"username": user.username,
|
||||
"name": user.name,
|
||||
"url": f"http://testserver/api/users/{user.username}/",
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
"""
|
||||
Module for all Form Tests.
|
||||
"""
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from image_markuper.users.forms import UserAdminCreationForm
|
||||
from image_markuper.users.models import User
|
||||
|
||||
|
||||
class TestUserAdminCreationForm:
|
||||
"""
|
||||
Test class for all tests related to the UserAdminCreationForm
|
||||
"""
|
||||
|
||||
def test_username_validation_error_msg(self, user: User):
|
||||
"""
|
||||
Tests UserAdminCreation Form's unique validator functions correctly by testing:
|
||||
1) A new user with an existing username cannot be added.
|
||||
2) Only 1 error is raised by the UserCreation Form
|
||||
3) The desired error message is raised
|
||||
"""
|
||||
|
||||
# The user already exists,
|
||||
# hence cannot be created.
|
||||
form = UserAdminCreationForm(
|
||||
{
|
||||
"username": user.username,
|
||||
"password1": user.password,
|
||||
"password2": user.password,
|
||||
}
|
||||
)
|
||||
|
||||
assert not form.is_valid()
|
||||
assert len(form.errors) == 1
|
||||
assert "username" in form.errors
|
||||
assert form.errors["username"][0] == _("This username has already been taken.")
|
|
@ -1,5 +0,0 @@
|
|||
from image_markuper.users.models import User
|
||||
|
||||
|
||||
def test_user_get_absolute_url(user: User):
|
||||
assert user.get_absolute_url() == f"/users/{user.username}/"
|
|
@ -1,21 +0,0 @@
|
|||
import pytest
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
def test_swagger_accessible_by_admin(admin_client):
|
||||
url = reverse("api-docs")
|
||||
response = admin_client.get(url)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_swagger_ui_not_accessible_by_normal_user(client):
|
||||
url = reverse("api-docs")
|
||||
response = client.get(url)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_api_schema_generated_successfully(admin_client):
|
||||
url = reverse("api-schema")
|
||||
response = admin_client.get(url)
|
||||
assert response.status_code == 200
|
|
@ -1,16 +0,0 @@
|
|||
import pytest
|
||||
from celery.result import EagerResult
|
||||
|
||||
from image_markuper.users.tasks import get_users_count
|
||||
from image_markuper.users.tests.factories import UserFactory
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_user_count(settings):
|
||||
"""A basic test to execute the get_users_count Celery task."""
|
||||
UserFactory.create_batch(3)
|
||||
settings.CELERY_TASK_ALWAYS_EAGER = True
|
||||
task_result = get_users_count.delay()
|
||||
assert isinstance(task_result, EagerResult)
|
||||
assert task_result.result == 3
|
|
@ -1,21 +0,0 @@
|
|||
from django.urls import resolve, reverse
|
||||
|
||||
from image_markuper.users.models import User
|
||||
|
||||
|
||||
def test_detail(user: User):
|
||||
assert (
|
||||
reverse("users:detail", kwargs={"username": user.username})
|
||||
== f"/users/{user.username}/"
|
||||
)
|
||||
assert resolve(f"/users/{user.username}/").view_name == "users:detail"
|
||||
|
||||
|
||||
def test_update():
|
||||
assert reverse("users:update") == "/users/~update/"
|
||||
assert resolve("/users/~update/").view_name == "users:update"
|
||||
|
||||
|
||||
def test_redirect():
|
||||
assert reverse("users:redirect") == "/users/~redirect/"
|
||||
assert resolve("/users/~redirect/").view_name == "users:redirect"
|
|
@ -1,103 +0,0 @@
|
|||
import pytest
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.contrib.messages.middleware import MessageMiddleware
|
||||
from django.contrib.sessions.middleware import SessionMiddleware
|
||||
from django.http import HttpRequest, HttpResponseRedirect
|
||||
from django.test import RequestFactory
|
||||
from django.urls import reverse
|
||||
|
||||
from image_markuper.users.forms import UserAdminChangeForm
|
||||
from image_markuper.users.models import User
|
||||
from image_markuper.users.tests.factories import UserFactory
|
||||
from image_markuper.users.views import (
|
||||
UserRedirectView,
|
||||
UserUpdateView,
|
||||
user_detail_view,
|
||||
)
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
class TestUserUpdateView:
|
||||
"""
|
||||
TODO:
|
||||
extracting view initialization code as class-scoped fixture
|
||||
would be great if only pytest-django supported non-function-scoped
|
||||
fixture db access -- this is a work-in-progress for now:
|
||||
https://github.com/pytest-dev/pytest-django/pull/258
|
||||
"""
|
||||
|
||||
def dummy_get_response(self, request: HttpRequest):
|
||||
return None
|
||||
|
||||
def test_get_success_url(self, user: User, rf: RequestFactory):
|
||||
view = UserUpdateView()
|
||||
request = rf.get("/fake-url/")
|
||||
request.user = user
|
||||
|
||||
view.request = request
|
||||
|
||||
assert view.get_success_url() == f"/users/{user.username}/"
|
||||
|
||||
def test_get_object(self, user: User, rf: RequestFactory):
|
||||
view = UserUpdateView()
|
||||
request = rf.get("/fake-url/")
|
||||
request.user = user
|
||||
|
||||
view.request = request
|
||||
|
||||
assert view.get_object() == user
|
||||
|
||||
def test_form_valid(self, user: User, rf: RequestFactory):
|
||||
view = UserUpdateView()
|
||||
request = rf.get("/fake-url/")
|
||||
|
||||
# Add the session/message middleware to the request
|
||||
SessionMiddleware(self.dummy_get_response).process_request(request)
|
||||
MessageMiddleware(self.dummy_get_response).process_request(request)
|
||||
request.user = user
|
||||
|
||||
view.request = request
|
||||
|
||||
# Initialize the form
|
||||
form = UserAdminChangeForm()
|
||||
form.cleaned_data = {}
|
||||
form.instance = user
|
||||
view.form_valid(form)
|
||||
|
||||
messages_sent = [m.message for m in messages.get_messages(request)]
|
||||
assert messages_sent == ["Information successfully updated"]
|
||||
|
||||
|
||||
class TestUserRedirectView:
|
||||
def test_get_redirect_url(self, user: User, rf: RequestFactory):
|
||||
view = UserRedirectView()
|
||||
request = rf.get("/fake-url")
|
||||
request.user = user
|
||||
|
||||
view.request = request
|
||||
|
||||
assert view.get_redirect_url() == f"/users/{user.username}/"
|
||||
|
||||
|
||||
class TestUserDetailView:
|
||||
def test_authenticated(self, user: User, rf: RequestFactory):
|
||||
request = rf.get("/fake-url/")
|
||||
request.user = UserFactory()
|
||||
|
||||
response = user_detail_view(request, username=user.username)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_not_authenticated(self, user: User, rf: RequestFactory):
|
||||
request = rf.get("/fake-url/")
|
||||
request.user = AnonymousUser()
|
||||
|
||||
response = user_detail_view(request, username=user.username)
|
||||
login_url = reverse(settings.LOGIN_URL)
|
||||
|
||||
assert isinstance(response, HttpResponseRedirect)
|
||||
assert response.status_code == 302
|
||||
assert response.url == f"{login_url}?next=/fake-url/"
|
|
@ -1,14 +0,0 @@
|
|||
from django.urls import path
|
||||
|
||||
from image_markuper.users.views import (
|
||||
user_detail_view,
|
||||
user_redirect_view,
|
||||
user_update_view,
|
||||
)
|
||||
|
||||
app_name = "users"
|
||||
urlpatterns = [
|
||||
path("~redirect/", view=user_redirect_view, name="redirect"),
|
||||
path("~update/", view=user_update_view, name="update"),
|
||||
path("<str:username>/", view=user_detail_view, name="detail"),
|
||||
]
|
|
@ -1,45 +0,0 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import DetailView, RedirectView, UpdateView
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class UserDetailView(LoginRequiredMixin, DetailView):
|
||||
|
||||
model = User
|
||||
slug_field = "username"
|
||||
slug_url_kwarg = "username"
|
||||
|
||||
|
||||
user_detail_view = UserDetailView.as_view()
|
||||
|
||||
|
||||
class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
|
||||
model = User
|
||||
fields = ["name"]
|
||||
success_message = _("Information successfully updated")
|
||||
|
||||
def get_success_url(self):
|
||||
return self.request.user.get_absolute_url()
|
||||
|
||||
def get_object(self):
|
||||
return self.request.user
|
||||
|
||||
|
||||
user_update_view = UserUpdateView.as_view()
|
||||
|
||||
|
||||
class UserRedirectView(LoginRequiredMixin, RedirectView):
|
||||
|
||||
permanent = False
|
||||
|
||||
def get_redirect_url(self):
|
||||
return reverse("users:detail", kwargs={"username": self.request.user.username})
|
||||
|
||||
|
||||
user_redirect_view = UserRedirectView.as_view()
|
Loading…
Reference in New Issue
Block a user