{"id":20345791,"url":"https://github.com/izzypt/interview_exercise_django","last_synced_at":"2026-06-11T14:31:02.585Z","repository":{"id":151595599,"uuid":"598837967","full_name":"izzypt/interview_exercise_django","owner":"izzypt","description":"Repo to store exercise solution","archived":false,"fork":false,"pushed_at":"2023-03-15T11:55:41.000Z","size":259,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-04T15:48:01.871Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/izzypt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-07T22:46:15.000Z","updated_at":"2023-03-15T11:54:56.000Z","dependencies_parsed_at":"2023-04-18T21:46:34.922Z","dependency_job_id":null,"html_url":"https://github.com/izzypt/interview_exercise_django","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/izzypt/interview_exercise_django","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/izzypt%2Finterview_exercise_django","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/izzypt%2Finterview_exercise_django/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/izzypt%2Finterview_exercise_django/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/izzypt%2Finterview_exercise_django/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/izzypt","download_url":"https://codeload.github.com/izzypt/interview_exercise_django/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/izzypt%2Finterview_exercise_django/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34204177,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-11T02:00:06.485Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-14T22:09:47.939Z","updated_at":"2026-06-11T14:31:02.560Z","avatar_url":"https://github.com/izzypt.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Índice\n\nÍndice das tarefas com o processo de resolução passo a passo\n\n- [Task 1](#Task1) - _Start a new Django project using a sqlite database._\n\n- [Task 2](#Task2) - _Create a app and add a Finding model to store the objects described_\n\n- [Task 3](#Task3) - _Create a Django command that retrieves all the findings for a given id and stores them in the database._\n\n- [Task 4](#Task4) - _Create a Django view to list and filter findings._\n\n\u003ca id=\"Task1\"\u003e\u003c/a\u003e\n\n# Task 1 \n\n1 - Criar uma nova directoria para o projecto.\n```\n\u003e\u003e\u003e mkdir intv_exercise\n```\n2 - Criar um ambiente virtual para o projecto de forma a manter as dependências isoladas.\n```\n\u003e\u003e\u003e python -m venv venv\n```\n3 - Activar ambiente virtual\n```\n\u003e\u003e\u003e .\\venv\\Scripts\\activate\n```\n4 - Instalar Django e Django Rest Framework\n```\n\u003e\u003e\u003e pip install Django\n\u003e\u003e\u003e pip install djangorestframework\n```\n5 - Registar as dependencias instaladas até ao momento num ficheiro chamado \"requirements.txt\".\n\n```\n\u003e\u003e\u003e pip freeze \u003e requirements.txt\n```\n\n6 - Iniciar o projecto Django\n```\n\u003e\u003e\u003e django-admin startproject backend\n````\n7 - Adicionar 'rest_framework' às ```INSTALLED_APPS``` no ficheiro ```settings.py```\n\n![image](https://user-images.githubusercontent.com/73948790/217391622-25aaf9a8-76b8-4e0d-adf5-03025c592e6d.png)\n\n8 - Django vem por padrão com uma BD SQlite nas settings , vamos criar as migrações necessárias para criar as nossa tabelas base\n```\n\u003e\u003e\u003e python manage.py makemigrations\n\u003e\u003e\u003e python manage.py migrate\n```\n\n\u003ca id=\"Task2\"\u003e\u003c/a\u003e\n\n# Task 2\n\n1 - Vamos criar uma nova app dentro do nosso projecto chamada \"findings\"\n```\npython .\\manage.py startapp findings\n```\n\n2 - Adicionamos a nova app às ```INSTALLED_APPS```\n\n![image](https://user-images.githubusercontent.com/73948790/217395540-42e27460-9b14-4a26-9776-b6d0ecd754b8.png)\n\n3 - Dentro da nossa nova app no ficheiro ```models.py``` vamos criar um novo modelo chamado ```FindingsModel```.\n\n   Findings são problemas de segurança encontrados durante o scan da app/api. \n\n   Cada finding vem com os dados recolhidos durante o scan, uma descrição da vulnerabilidade e uma sugestão de como corrigi-la. \n\n   O nosso modelo deve conter :\n\n       ● id -\u003e The finding id\n  \n       ● target_id -\u003e String (unique Base58 value)\n  \n       ● definition_id -\u003e String (unique Base58 value)\n  \n       ● scans -\u003e Array of strings (unique Base58 value)\n  \n       ● url -\u003e String\n  \n       ● path -\u003e String\n  \n       ● method -\u003e string\n   \n\nO nosso diagrama de UML para o presente modelo, fica assim :\n\n![image](https://user-images.githubusercontent.com/73948790/217618686-8a88d253-4701-4b3d-9640-a1e58bc00dd4.png)\n\nEm alternativa, se o campo \"scans\" não fosse obrigatório no nosso ```findingsModel```, poderíamos ter criado algo como :\n\n![image](https://user-images.githubusercontent.com/73948790/217619088-93bd1b6d-aa0d-476f-93a3-223568b67ee0.png)\n\nO nosso modelo fica assim:\n```\n\tclass ScanModel(models.Model):\n\t   id = models.AutoField(primary_key=True)\n\t   value = models.CharField(max_length=100)\n\n\t   def __str__(self):\n\t\t   return self.value\n\n\tclass FindingsModel(models.Model):\n\t\tHTTP_METHODS = (\n\t\t\t('GET', 'GET'),\n\t\t\t('POST', 'POST'),\n\t\t\t('PUT', 'PUT'),\n\t\t\t('PATCH', 'PATCH'),\n\t\t\t('DELETE', 'DELETE'),\n\t\t)\n\t\tid = models.AutoField(primary_key=True)\n\t\ttarget_id = models.CharField(max_length=100)\n\t\tdefinition_id = models.CharField(max_length=100)\n\t\tscans = models.ManyToManyField(ScanModel)\n\t\turl = models.URLField()\n\t\tpath = models.CharField(max_length=200)\n\t\tmethod = models.CharField(max_length=10, choices=HTTP_METHODS)\n\n\t\tdef __str__(self):\n\t\t   return self.target_id\n```       \n4 - Fazer as migrações e migrar os nossos novos modelos.\n\n```\n\u003e\u003e\u003e python manage.py makemigrations\n\u003e\u003e\u003e python manage.py migrate\n```\n\n\u003ca id=\"Task3\"\u003e\u003c/a\u003e\n\n# Task 3\n\n1 - Vamos criar uma nova directoria na nossa app ```findings``` : management/commands. \n\n2 - Criar um novo ficheiro chamado ```get_findings.py``` que servirá como comando para acedermos através do ```manage.py```\n\n3 - Iremos fazer a chamada a API e guardar a resposta na nossa BD:\n\n```\nfrom django.core.management.base import BaseCommand, CommandError\nfrom django.conf import settings\nfrom findings.models import FindingsModel, ScanModel\nimport requests\nimport json\n\nclass Command(BaseCommand):\n    help = 'Fetch the API findings from the \"List Findings\" endpoint'\n    \n    def add_arguments(self, parser):\n        parser.add_argument('target_id', type=str, help='The target ID of the scan', default=\"Tt2f8EyPSTwq\")\n    \n    def handle(self, *args, **kwargs):\n        target_id = kwargs['target_id']\n        results = self.make_api_call(target_id)\n        self.save_to_db(results)\n    \n    def make_api_call(self, target_id : str) -\u003e dict:\n        url = f\"https://api.probely.com/targets/{target_id}/findings/\"\n        headers  = {\n            \"Content-Type\": \"application/json\",\n            \"Authorization\": f\"JWT {settings.PROBELY_API_KEY}\"\n        }\n        #Make the request and get the response\n        response = requests.get(url, headers=headers)\n        if response.status_code != 200:\n            raise CommandError(f\"Was expecting 200 status code from API, instead got {response.status_code}\")\n        results = response.json().get(\"results\")\n        return results\n    \n    def save_to_db(self, results : dict) -\u003e None:\n        for finding in results:\n            try:\n                finding_id = finding.get(\"id\")\n                target_id = finding[\"target\"].get(\"id\")\n                definition_id = finding[\"definition\"].get(\"id\")\n                scans = finding.get(\"scans\", [])\n                url = finding.get(\"url\")\n                path = finding.get(\"path\")\n                method = finding.get(\"method\")\n            except Exception as e:\n                raise CommandError(f\"Something went wrong while extracting the data from the API response\", e)\n            try:\n                # Create or retrieve the Scan instances\n                scan_instances = [ScanModel.objects.get_or_create(value=scan_value)[0] for scan_value in scans]\n\n                # Create or retrieve the Finding instance\n                # get_or_create method returns a tuple of two values: \n                # (1 - The instance that matches the query, 2 - True/False. A boolean indicating whether the instance was created or not.)\n                finding, created = FindingsModel.objects.get_or_create(\n                    id=finding_id,\n                    target_id=target_id,\n                    definition_id=definition_id,\n                    url=url,\n                    path=path,\n                    method=method\n                )\n                if created:\n                    self.stdout.write(self.style.SUCCESS(f\"Successfully created finding {finding.id}\"))\n                else:\n                    self.stdout.write(self.style.WARNING(f\"Finding ID {finding.id} already in the database\"))\n                # Update the scans field for the finding\n                finding.scans.add(*scan_instances)\n            except Exception as e:\n                raise CommandError(f\"Something went wrong while creating or fetching the finding in the database:\", e)\n```\n\n\u003ca id=\"Task4\"\u003e\u003c/a\u003e\n\n# Task 4\n1 - Vamos criar os urls, as views e os serializers necessários para servir os dados que guardámos na BD.\n\n2 - No nossa directoria mãe , importamos os url que vamos definir na nossa ```findings``` app.\n```\nfrom django.urls import path, include\n\nurlpatterns = [\n    path('admin/', admin.site.urls),\n    path('findings/', include('findings.urls')),\n]\n```\n\n3 - Vamos definir os URLS que acabámos de importar no ficheiro ```urls.py``` da nossa app.\n```\nfrom django.urls import path, include\nfrom .views import FindingsList\n\nurlpatterns = [\n    path('list/', FindingsList.as_view(), name='findings_list'),\n]\n```\n4 - Vamos criar os serializers responsáveis por fazerem parse dos nossos dados no ficheiro ```serializers.py``` dentro da nossa app.\n```\nfrom rest_framework import serializers\nfrom findings.models import FindingsModel, ScanModel\n\nclass ScanSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = ScanModel\n        fields = ('value',)\n    \n    def to_representation(self, instance):\n        # ModelSerializer serializes a model instance to a dictionary-like object, we need to override that behavior because we want to return a string for each scan.\n        # to_representation allows to define how the object should be serialized and what data should be included in the serialized representation.\n        return instance.value\n\nclass FindingsSerializer(serializers.ModelSerializer):\n    scans = ScanSerializer(many=True)\n    \n    class Meta:\n        model = FindingsModel\n        fields = ('id', 'definition_id', 'target_id', 'url', 'path', 'method', 'scans')\n```\n\n5- Por fim , necessitamos da nossa view que é responsável por lidar com o pedido que for enviado para o caminho especificado:\n\n```\nfrom rest_framework import status\nfrom rest_framework.response import Response\nfrom rest_framework.views import APIView\nfrom findings.models import FindingsModel, ScanModel\nfrom .serializers import FindingsSerializer\n\n\n# Create your views here.\nclass FindingsList(APIView):\n    def get(self, request, format=None):\n        # Get query parameters if any\n        definition_id = request.GET.get('definition_id')\n        scan_value = request.GET.get('scan_value')\n        \n        # Get all findings\n        findings = FindingsModel.objects.all()\n        if definition_id:\n            findings = findings.filter(definition_id=definition_id)\n        if scan_value:\n            findings = findings.filter(scans__value=scan_value)\n\n        # Serialize findings and respond\n        serializer = FindingsSerializer(findings, many=True)\n        if len(serializer.data) == 0:\n            return Response({'Message' : 'No content found'},status=status.HTTP_404_NOT_FOUND)\n        else:\n            return Response(serializer.data, status=status.HTTP_200_OK)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fizzypt%2Finterview_exercise_django","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fizzypt%2Finterview_exercise_django","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fizzypt%2Finterview_exercise_django/lists"}