Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions profile-specific-challenges/backend/custom_rest_framework/Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
django = "*"
djangorestframework = "*"

[dev-packages]

[requires]
python_version = "3.12"

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Custom Model Serializer

Your task is to implement a custom version of the `ModelSerializer` class, originally provided by the rest_framework library, with the following features:

1. The serializer should automatically define its fields based on the `fields` parameter in the `Meta` class.
2. Implement support for the following field types: `CharField`, `IntegerField`, `BooleanField`, `FloatField`, `EmailField` and `SlugField`
3. Implement the `read_only` and `write_only` parameters for the fields.
4. Implement proper validations for: `IntegerField`, `BooleanField`, `EmailField`, `SlugField`
5. Implement the following properties and methods:
- .data - Returns the outgoing primitive representation.
- .is_valid() - Deserializes and validates incoming data.
- .validated_data - Returns the validated incoming data.
- .errors - Returns any errors during validation.
- .save() - Persists the validated data into an object instance.
- .to_representation(instance) - for read operations.
- .to_internal_value(data) - for write operations.
- .create(validated_data) and .update(instance, validated_data) - to support saving instances.

The app `custom_rest_framework` has already been created with a dummy model named `TestSerializerModel` with the necessary fields. Additionally, a test file has been pre-configured, containing all the necessary test cases.

You should change the import from the original `restframework` and import your custom one while having the test still working fine.

For running the tests you should first install `Django` and `restframework`

```
$ pipenv install
$ pipenv shell
$ python manage.py test
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class RestframeworkConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "custom_rest_framework"
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Generated by Django 5.1.4 on 2025-01-10 12:01

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = []

operations = [
migrations.CreateModel(
name="TestSerializerModel",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("text", models.CharField(max_length=50)),
("number", models.IntegerField()),
("is_something", models.BooleanField()),
("email", models.CharField(max_length=256)),
("slug", models.CharField(max_length=64)),
("real", models.FloatField()),
("password", models.CharField(max_length=50)),
("generated", models.CharField(max_length=256)),
],
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.db import models


class TestSerializerModel(models.Model):
text = models.CharField(max_length=50)
number = models.IntegerField()
is_something = models.BooleanField()
email = models.CharField(max_length=256)
slug = models.CharField(max_length=64)
real = models.FloatField()
password = models.CharField(max_length=50)
generated = models.CharField(max_length=256)

def save(self, *args, **kwargs):
self.generated = f"{self.slug}:{str(self.number)}"
super().save()
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import random
from typing import Any

from django.test import TestCase
from rest_framework.serializers import EmailField, ModelSerializer, SlugField, CharField

from .models import TestSerializerModel
# from .serializers import EmailField, ModelSerializer, SlugField, CharField


# Create your tests here.
class TestModelSerializer(ModelSerializer):
email = EmailField()
slug = SlugField()
password = CharField(write_only=True)

class Meta:
model = TestSerializerModel
fields = [
"text",
"number",
"is_something",
"email",
"slug",
"real",
"password",
"generated",
]
read_only_fields = ["generated"]


class TestSerializer(TestCase):
def create_random_data(self) -> dict:
"""Creates random data for test purposes."""
return dict(
text=str(random.randint(1000, 2000)),
number=random.randint(0, 1000),
is_something=random.choices([True, False])[0],
slug=str(random.randint(1000, 2000)),
email=f"{str(random.randint(10000, 20000))}@{random.randint(1000, 2000)}.com",
real=random.randint(1000, 9999) / 100,
password=str(random.randint(100000, 999999)),
)

def assertInstance(self, instance: Any, data: dict, exclude: list = list()):
"""Assert that all the values in the dictionary exists as a property in the
instance."""
if data and instance:
for k, v in data.items():
if k not in exclude:
assert hasattr(instance, k), (
f"Property {k} should exist in instance"
)
assert getattr(instance, k) == v, (
f"Property value '{v}' should be equals to instance value"
)

def assertEqualDictionary(self, left: dict, right: dict, exclude: list = list()):
"""Assert that all the values in the right should exists in the left."""
if right is not None and left is not None:
for k, v in right.items():
if k not in exclude:
assert left.get(k) is not None, (
f"Property {k} should be in left dictionary"
)
assert left.get(k) == v, (
f"Property value '{left.get(k)}' should be equals to right value {v}"
)

for k, v in left.items():
if k not in exclude:
assert right.get(k) is not None, (
f"Property {k} should be in right dictionary"
)
assert right.get(k) == v, (
f"Property value '{left.get(k)}' should be equals to right value {v}"
)

def test_1_read(self):
"""We create a model on DB and then check the serialized data is correct."""
data = self.create_random_data()
instance = TestSerializerModel.objects.create(**data)
serializer = TestModelSerializer(instance)
self.assertIsNotNone(serializer.data, "Serializer should return some data")
self.assertInstance(instance, serializer.data)
self.assertEqualDictionary(serializer.data, data, ["password", "generated"])

def test_2_create(self):
"""We create a model through the serializer and check that it was created
correctly in DB."""
data = self.create_random_data()
serializer = TestModelSerializer(data=data)
self.assertTrue(serializer.is_valid())
serializer.save()
instance = TestSerializerModel.objects.last()
self.assertInstance(instance, data)

def test_3_update(self):
"""We update a model through the serializer and check that the values were
correctly updated in DB."""
data = self.create_random_data()
instance = TestSerializerModel.objects.create(**data)
previous_count = TestSerializerModel.objects.count()
new_data = self.create_random_data()
serializer = TestModelSerializer(instance, data=new_data)
self.assertTrue(serializer.is_valid())
serializer.save()
self.assertEqual(previous_count, TestSerializerModel.objects.count())
new_instance = TestSerializerModel.objects.get(id=instance.id)
self.assertInstance(new_instance, new_data)

def test_4_error_int(self):
"""We force an error on creation on the number field."""
data = self.create_random_data()
data["number"] = "15.5"
serializer = TestModelSerializer(data=data)
self.assertFalse(serializer.is_valid())
self.assertIsNotNone(serializer.errors)
self.assertEqual(len(serializer.errors), 1)
self.assertTrue("number" in serializer.errors)

def test_5_error_bool(self):
"""We force an error on creation on the bool field."""
data = self.create_random_data()
data["is_something"] = "jojo"
serializer = TestModelSerializer(data=data)
self.assertFalse(serializer.is_valid())
self.assertIsNotNone(serializer.errors)
self.assertEqual(len(serializer.errors), 1)
self.assertTrue("is_something" in serializer.errors)

def test_6_error_slug(self):
"""We force an error on creation on the slug field."""
data = self.create_random_data()
data["slug"] = "not_an_slug./$"
serializer = TestModelSerializer(data=data)
self.assertFalse(serializer.is_valid())
self.assertIsNotNone(serializer.errors)
self.assertEqual(len(serializer.errors), 1)
self.assertTrue("slug" in serializer.errors)

def test_7_error_email(self):
"""We force an error on creation on the email field."""
data = self.create_random_data()
data["email"] = "not_an_email"
serializer = TestModelSerializer(data=data)
self.assertFalse(serializer.is_valid())
self.assertIsNotNone(serializer.errors)
self.assertEqual(len(serializer.errors), 1)
self.assertTrue("email" in serializer.errors)

def test_8_write_only(self):
"""We create a model on DB and then check the serialized data without the
write only field."""
data = self.create_random_data()
instance = TestSerializerModel.objects.create(**data)
serializer = TestModelSerializer(instance)
serialized_data = serializer.data
self.assertIsNotNone(serialized_data)
self.assertInstance(instance, serialized_data)
self.assertFalse("password" in serialized_data)
self.assertEqualDictionary(serializer.data, data, ["password", "generated"])

def test_9_read_only(self):
"""We create a model through the serializer and check that it was created
correctly skipping the read only field."""
data = self.create_random_data()
data["generated"] = "fixed" # we write a wrong value in a generated field
serializer = TestModelSerializer(data=data)
self.assertTrue(serializer.is_valid())
serializer.save()
instance = TestSerializerModel.objects.last()
self.assertInstance(instance, data, ["generated"])
self.assertNotEqual(instance.generated, data["generated"])
self.assertEqual(instance.generated, f"{data['slug']}:{str(data['number'])}")
self.assertEqualDictionary(serializer.data, data, ["password", "generated"])
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.shortcuts import render

# Create your views here.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
"""Run administrative tasks."""
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
ASGI config for server project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings")

application = get_asgi_application()
Loading