Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

регистрация компании #62

Merged
merged 40 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
06ddf43
Add SoftDeleteMixin, CustomManager, migration
ByJIaT Aug 14, 2023
74fc1dd
Add djoser, pytest. pytest settings
ByJIaT Aug 14, 2023
c619e50
Add users urls
ByJIaT Aug 14, 2023
3a0ebf9
Add users tests
ByJIaT Aug 14, 2023
e0bac06
Add djoser settings
ByJIaT Aug 14, 2023
087b028
Add CustomUserViewSet, companies endpoints
ByJIaT Aug 14, 2023
0bf300f
Add CompanyCreateSerializer, BaseSerializer, CompanySerializer, Addre…
ByJIaT Aug 14, 2023
2e6ea2b
Fix resolves
ByJIaT Aug 14, 2023
a50c09c
Add field AddressSerializer
ByJIaT Aug 14, 2023
d35139a
Add docstring PhoneNumberSerializer
ByJIaT Aug 14, 2023
17b1f1d
Add docstring PhoneNumberSerializer
ByJIaT Aug 14, 2023
ba5f094
Add get_serializer_class get_serializer_class
ByJIaT Aug 14, 2023
d2a367a
Changed directory
ByJIaT Aug 14, 2023
374e496
Changed directory
ByJIaT Aug 14, 2023
1df8a11
removed unused fields
ByJIaT Aug 14, 2023
b9fde9f
add email login field djoser settings
ByJIaT Aug 14, 2023
b580248
add docstrings BaseSerializer
ByJIaT Aug 14, 2023
37d30eb
add docstrings companies serializers
ByJIaT Aug 14, 2023
63d205d
add user delete test
ByJIaT Aug 14, 2023
49e2c14
add user deleting
ByJIaT Aug 14, 2023
07bcd2d
refactor url patterns
ByJIaT Aug 14, 2023
523e499
poetry.lock
ByJIaT Aug 14, 2023
664793f
Merge branch develop
ByJIaT Aug 14, 2023
8d177a2
Change UserCompanyReadSerializer name
ByJIaT Aug 15, 2023
aa59e15
Change UserCompanyWriteSerializer name
ByJIaT Aug 15, 2023
22e8856
Add DynamicRouter
ByJIaT Aug 15, 2023
532e84c
Change router in users
ByJIaT Aug 15, 2023
61c03a6
Reduced the number of queries get_companies to the database
ByJIaT Aug 15, 2023
9be6e22
Add schema pagination result
ByJIaT Aug 15, 2023
bc6533d
Del "company_account" from CompanyReadSerializer
ByJIaT Aug 15, 2023
9bc8747
Add null=True ogrn, company_account fields Company model
ByJIaT Aug 16, 2023
c30a448
changed python version 3.9 -> 3.11
ByJIaT Aug 16, 2023
5f8debd
Merge develop branch
ByJIaT Aug 16, 2023
f8bdc3c
changed python version
ByJIaT Aug 16, 2023
bd00839
changed python version
ByJIaT Aug 16, 2023
b3baab6
merge migrations 0002_alter_address_options_alter_address_address_and…
ByJIaT Aug 16, 2023
88c82bb
add blank=true company_account Company
ByJIaT Aug 16, 2023
7fb33e1
add blank=true ogrn Company
ByJIaT Aug 16, 2023
eca2a23
add blank=true ogrn Company
ByJIaT Aug 16, 2023
cc44289
fix users tests utils
ByJIaT Aug 16, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion apps/users/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,13 @@ class CustomUserAdmin(UserAdmin):
"username",
"email",
"is_company",
"is_active",
"is_deleted",
)
list_filter = (
"is_company",
"is_deleted",
)
list_filter = ("is_company",)
empty_value_display = "-empty-"


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 4.1 on 2023-08-14 07:17

from django.db import migrations, models

import apps.users.models


class Migration(migrations.Migration):
dependencies = [
("users", "0001_initial"),
]

operations = [
migrations.AlterModelManagers(
name="customuser",
managers=[
("objects", apps.users.models.CustomUserManager()),
],
),
migrations.AddField(
model_name="customuser",
name="is_deleted",
field=models.BooleanField(default=False),
),
]
14 changes: 12 additions & 2 deletions apps/users/models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import re

from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import AbstractUser, UserManager
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _

from apps.core.models import SoftDeleteMixin


def validate_username(value):
pattern = r"^[\w.@+-]+$"
Expand Down Expand Up @@ -128,7 +130,12 @@ def __str__(self):
return f"{self.first_name} {self.last_name}"


class CustomUser(AbstractUser):
class CustomUserManager(UserManager):
def get_companies(self):
return self.exclude(company=None)
ByJIaT marked this conversation as resolved.
Show resolved Hide resolved


class CustomUser(SoftDeleteMixin, AbstractUser):
ByJIaT marked this conversation as resolved.
Show resolved Hide resolved
email = models.EmailField(unique=True, blank=False, max_length=254, verbose_name="Email")
username = models.CharField(
max_length=150,
Expand All @@ -154,13 +161,16 @@ class CustomUser(AbstractUser):
on_delete=models.SET_NULL,
)

objects = CustomUserManager()

class Meta:
swappable = "AUTH_USER_MODEL"
ordering = ("username",)
verbose_name = _("User")
verbose_name_plural = _("Users")

def clean(self):
super().clean()
if self.is_company and not self.company:
raise ValidationError(_("Company field is required for companies!"))
if not (self.is_superuser or self.is_staff) and not (self.is_company or self.personal):
Expand Down
95 changes: 95 additions & 0 deletions apps/users/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
from django.contrib.auth import get_user_model
from django.db import transaction
from djoser.conf import settings
from djoser.serializers import UserCreateSerializer
from rest_framework import serializers

from apps.users.models import Address, Company, PhoneNumber

User = get_user_model()


class PhoneNumberSerializer(serializers.ModelSerializer):
ByJIaT marked this conversation as resolved.
Show resolved Hide resolved
class Meta:
model = PhoneNumber
fields = "__all__"


class AddressSerializer(serializers.ModelSerializer):
class Meta:
model = Address
fields = "__all__"


class AddressPhoneSerializer(serializers.ModelSerializer):
address = AddressSerializer()
phone_number = PhoneNumberSerializer()

class Meta:
abstract = True


class CompanySerializer(AddressPhoneSerializer):
class Meta:
model = Company
fields = "__all__"
ByJIaT marked this conversation as resolved.
Show resolved Hide resolved
ByJIaT marked this conversation as resolved.
Show resolved Hide resolved


class BaseSerializer(UserCreateSerializer):
class Meta:
model = User

def validate(self, attrs):
return attrs
ByJIaT marked this conversation as resolved.
Show resolved Hide resolved

def perform_create(self, validated_data, **kwargs):
ByJIaT marked this conversation as resolved.
Show resolved Hide resolved
with transaction.atomic():
user = User.objects.create_user(**validated_data, **kwargs)
if settings.SEND_ACTIVATION_EMAIL:
user.is_active = False
user.save(update_fields=["is_active"])
return user

def _address_phone_attach(self, obj):
address = obj.pop("address", None)
phone_number = obj.pop("phone_number", None)

address_obj = Address.objects.create(**address)
phone_number_obj = PhoneNumber.objects.create(**phone_number)

return {"address": address_obj, "phone_number": phone_number_obj}

def _extract_relations(self, validated_data):
reverse_relations = {}
for field_name, field in self.fields.items():
if isinstance(field, serializers.ModelSerializer):
if field.source not in validated_data:
continue

reverse_relations[field_name] = (
self.fields[field_name].Meta.model,
validated_data.pop(field_name),
)
return reverse_relations

def update_or_create(self, validated_data):
reverse_relations = {}
relations = self._extract_relations(validated_data)

for field_name, (model, data) in relations.items():
with transaction.atomic():
address_phone = self._address_phone_attach(data)
reverse_relations[field_name] = model.objects.create(**data, **address_phone)
return reverse_relations


class CompanyCreateSerializer(BaseSerializer):
company = CompanySerializer()

class Meta(BaseSerializer.Meta):
fields = ("id", "email", "username", "is_company", "company", "password")

@transaction.atomic
def create(self, validated_data):
relations = self.update_or_create(validated_data)
return self.perform_create(validated_data, **relations, is_company=True)
Empty file added apps/users/tests/__init__.py
Empty file.
38 changes: 38 additions & 0 deletions apps/users/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import pytest
from rest_framework.test import APIClient

from apps.users.models import Address, Company, PhoneNumber


@pytest.fixture
def apiclient():
return APIClient()


@pytest.fixture
def company(django_user_model):
phone_number = PhoneNumber.objects.create(phone_number="1234567")
address = Address.objects.create(address="earth")
company_obj = Company.objects.create(
role="supplier",
name="best_company",
company_account="12345678901234567890",
inn="1234567890",
ogrn="1234567890123",
phone_number=phone_number,
address=address,
)
return django_user_model.objects.create_user(
email="[email protected]",
username="companyuser",
password="12345678",
is_company=True,
company=company_obj,
)


@pytest.fixture
def company_client(company):
client = APIClient()
client.force_authenticate(user=company)
return client
132 changes: 132 additions & 0 deletions apps/users/tests/test_00_company_registration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import pytest
from django.core import mail
from rest_framework import status

from apps.users.tests.utils import (
invalid_data_for_company_account_inn_ogrn,
request_valid_data,
response_valid_data_company_registration,
)


@pytest.mark.django_db(transaction=True)
class Test00CompanyRegistration:
url_signup = "/api/v1/users/companies/"
activate_url = "/api/v1/users/activation/"
login_url = "/api/v1/auth/token/login/"
user_me_url = "/api/v1/users/me/"

def test_00_nodata_signup(self, apiclient):
response = apiclient.post(self.url_signup, format="json")

assert response.status_code != status.HTTP_404_NOT_FOUND

assert response.status_code == status.HTTP_400_BAD_REQUEST

response_json = response.json()
empty_fields = ["email", "username", "company"]
for field in empty_fields:
assert field in response_json and isinstance(response_json.get(field), list)

def test_00_invalid_data_signup(self, apiclient, django_user_model):
invalid_data = {
"email": "invalid_email",
"username": " ",
"company": {
"role": "customer",
"name": " ",
"company_account": "20only_twenty_digits",
"inn": "only_10_di",
"ogrn": "only_13_digit",
"address": {"address": "address"},
"phone_number": {"phone_number": "1234567"},
},
}

users_count = django_user_model.objects.count()
response = apiclient.post(self.url_signup, data=invalid_data, format="json")

assert response.status_code != status.HTTP_404_NOT_FOUND

assert response.status_code == status.HTTP_400_BAD_REQUEST

assert users_count == django_user_model.objects.count()

@pytest.mark.parametrize("data,message", invalid_data_for_company_account_inn_ogrn)
def test_00_signup_length_and_simbols_validation(
self, apiclient, data, message, django_user_model
):
users_count = django_user_model.objects.count()
response = apiclient.post(self.url_signup, data=data, format="json")

assert response.status_code == status.HTTP_400_BAD_REQUEST, message

assert users_count == django_user_model.objects.count()

def test_00_valid_data_company_signup(self, apiclient, django_user_model):
outbox_before_count = len(mail.outbox)

response = apiclient.post(self.url_signup, data=request_valid_data, format="json")
outbox_after = mail.outbox

assert response.status_code != status.HTTP_404_NOT_FOUND

assert response.status_code == status.HTTP_201_CREATED

assert response.json() == response_valid_data_company_registration

new_user = django_user_model.objects.filter(
email=response_valid_data_company_registration["email"]
)
assert new_user.exists()

assert len(outbox_after) == outbox_before_count + 1

new_user.delete()

def test_00_01_valid_data_company_signup_activation(self, apiclient):
response = apiclient.post(self.url_signup, data=request_valid_data, format="json")
assert len(mail.outbox) == 1

email_lines = mail.outbox[0].body.splitlines()
activation_link = [line for line in email_lines if "/activate/" in line][0]
uid, token = activation_link.split("/")[-2:]
data = {"uid": uid, "token": token}

response = apiclient.post(self.activate_url, data=data, format="json")
assert response.status_code == status.HTTP_204_NO_CONTENT

login_data = {
"username": "valid-username",
"password": "12345678",
}

response = apiclient.post(self.login_url, data=login_data, format="json")
assert "auth_token" in response.json()

token = response.json()["auth_token"]

response = apiclient.get(self.user_me_url)
assert response.status_code == status.HTTP_401_UNAUTHORIZED

apiclient.credentials(HTTP_AUTHORIZATION=f"Token {token}")

response = apiclient.get(self.user_me_url)
assert response.status_code == status.HTTP_200_OK

def test_00_registration_me_username_restricted(self, apiclient):
valid_data = {
"email": "[email protected]",
"username": "me",
"company": {
"role": "customer",
"name": "valid-name",
"company_account": "12345678901234567890",
"inn": "1234567890",
"ogrn": "1234567890123",
"address": {"address": "address"},
"phone_number": {"phone_number": "1234567"},
},
}
response = apiclient.post(self.url_signup, data=valid_data, format="json")
assert response.status_code == status.HTTP_400_BAD_REQUEST
40 changes: 40 additions & 0 deletions apps/users/tests/test_01_companies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import pytest
from rest_framework import status


@pytest.mark.django_db(transaction=True)
class Test01CompanyAPI:
companies_url = "/api/v1/users/companies/"
user_me_url = "/api/v1/users/me/"

def test_01_users_get_paginated_companies(self, apiclient, company):
response = apiclient.get(self.companies_url)

assert response.status_code != status.HTTP_404_NOT_FOUND
assert response.status_code == status.HTTP_200_OK

data = response.json()
expected_keys = ("count", "next", "previous", "results")

for company in data["results"]:
assert company["is_company"] is True

for key in expected_keys:
assert key in data

assert data["count"] == 1

def test_01_users_delete(self, apiclient, company_client, company):
response = apiclient.delete(self.user_me_url, format="json")
assert response.status_code == status.HTTP_401_UNAUTHORIZED

login_data = {
"current_password": "12345678",
}

assert company.is_deleted is False

response = company_client.delete(self.user_me_url, data=login_data, format="json")

assert response.status_code == status.HTTP_204_NO_CONTENT
assert company.is_deleted is True
Loading