Skip to content
Open
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
2 changes: 1 addition & 1 deletion compose/production/django/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

FROM python:3.8-slim-buster
FROM python:3.8-slim

ENV PYTHONUNBUFFERED 1

Expand Down
1 change: 1 addition & 0 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,4 @@
CORS_URLS_REGEX = r"^/api/.*$"
# Your stuff...
# ------------------------------------------------------------------------------
VALIDATE_SPEC = env.bool("VALIDATE_SPEC", True)
18 changes: 17 additions & 1 deletion openapi_documentor/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import pytest

from django.conf import settings
from openapi_documentor.users.models import User
from openapi_documentor.users.tests.factories import UserFactory
from openapi_documentor.openapi.tests.factories import DocumentFactory


open_api_file = settings.APPS_DIR / "openapi/tests/fixtures/hello.yaml"
with open(open_api_file) as fout:
open_api_doc = fout.read()


@pytest.fixture(autouse=True)
Expand All @@ -12,3 +18,13 @@ def media_storage(settings, tmpdir):
@pytest.fixture
def user() -> User:
return UserFactory()


@pytest.fixture
def document(openapi) -> User:
return DocumentFactory(doc=openapi)


@pytest.fixture
def openapi() -> str:
return open_api_doc
16 changes: 15 additions & 1 deletion openapi_documentor/openapi/admin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from django.contrib import admin
from django.db.models import Q
from django.http.request import HttpRequest
from django.http.response import HttpResponse

# Register your models here.
# from guardian.admin import GuardedModelAdmin
Expand All @@ -19,4 +22,15 @@ def get_queryset(self, request):
qs = super(DocumentAdmin, self).get_queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(owner=request.user)
return qs.filter(Q(owner=request.user) | Q(editors=request.user)).distinct()

def change_view(
self, request: HttpRequest, object_id: str, **kwargs
) -> HttpResponse:
if not (
request.user.is_superuser
or request.user.document_set.filter(pk=object_id).exists()
):
self.exclude = ["editors"]
self.readonly_fields = ["owner"]
return super().change_view(request, object_id, **kwargs)
30 changes: 30 additions & 0 deletions openapi_documentor/openapi/migrations/0007_auto_20230630_1414.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 3.1.7 on 2023-06-30 14:14

from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('openapi', '0006_auto_20210321_1802'),
]

operations = [
migrations.AddField(
model_name='document',
name='editors',
field=models.ManyToManyField(blank=True, default=None, related_name='editors', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='document',
name='created',
field=models.DateTimeField(auto_now_add=True, verbose_name='date published'),
),
migrations.AlterField(
model_name='document',
name='modified',
field=models.DateTimeField(auto_now=True, verbose_name='date modified'),
),
]
12 changes: 8 additions & 4 deletions openapi_documentor/openapi/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
import yaml

try:
from yaml import CLoader as YamlLoader
from yaml import Loader as YamlLoader
except ImportError:
from yaml import YamlLoader

from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from openapi_spec_validator import validate_spec
from taggit.managers import TaggableManager
Expand Down Expand Up @@ -45,8 +46,9 @@ class Document(models.Model):
default=OpenapiVersions.THREEZERO,
) # oas 3.0
owner = models.ForeignKey(User, on_delete=models.DO_NOTHING, default=None)
created = models.DateTimeField("date published")
modified = models.DateTimeField("date modified")
created = models.DateTimeField("date published", auto_now_add=True)
modified = models.DateTimeField("date modified", auto_now=True)
editors = models.ManyToManyField(User, default=None, related_name="editors", blank=True)
tags = TaggableManager(through=TaggedDocument)

class Meta:
Expand All @@ -56,6 +58,8 @@ def clean(self):
parsed_doc = self._parse_doc(self.doc)
if not parsed_doc:
raise ValidationError(_("Only Json and Yaml are allowed"))
if not settings.VALIDATE_SPEC:
return
try:
validate_spec(parsed_doc)
except: # noqa: E722
Expand All @@ -64,7 +68,7 @@ def clean(self):
def save(self, *args, **kwargs):
parsed_doc = self._parse_doc(self.doc)
if parsed_doc:
self.formatted = json.dumps(parsed_doc)
self.formatted = json.dumps(parsed_doc, default=str)
super().save(*args, **kwargs)

def get_absolute_url(self):
Expand Down
10 changes: 10 additions & 0 deletions openapi_documentor/openapi/tests/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from factory import Faker
from factory.django import DjangoModelFactory
from openapi_documentor.openapi.models import Document


class DocumentFactory(DjangoModelFactory):
title = Faker("user_name")

class Meta:
model = Document
15 changes: 15 additions & 0 deletions openapi_documentor/openapi/tests/fixtures/hello.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
openapi: 3.0.0
info:
title: Minimal API
version: 1.0.0
paths:
/hello:
get:
summary: Retrieve a greeting message
responses:
"200":
description: Successful response
content:
text/plain:
schema:
type: string
52 changes: 52 additions & 0 deletions openapi_documentor/openapi/tests/test_admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from unittest.mock import Mock

import pytest
from django.urls import reverse
from django.contrib.admin.sites import AdminSite

from openapi_documentor.openapi.models import Document
from openapi_documentor.openapi.admin import DocumentAdmin
from openapi_documentor.openapi.tests.factories import DocumentFactory
from openapi_documentor.users.tests.factories import UserFactory

pytestmark = pytest.mark.django_db


class TestDocumentAdmin:
def test_add(self, admin_client, user, openapi):
url = reverse("admin:openapi_document_add")
response = admin_client.get(url)
assert response.status_code == 200

response = admin_client.post(
url,
data={
"title": "MyApiDoc",
"doc": openapi,
"owner": user.pk,
"version": "3.0.0",
"rev": "1.0.0",
"tags": ["test"],
},
)
assert response.status_code == 302
assert Document.objects.filter(title="MyApiDoc", owner=user).exists()

def test_list_docs_with_editor_access(self):
user1 = UserFactory()
user2 = UserFactory()
u1_private_doc = DocumentFactory.create_batch(3, owner=user1)
u1_shared = DocumentFactory.create_batch(2, owner=user1)
# user1 shared editor access with user2
for d in u1_shared:
d.editors.add(user2)

u2_private_doc = DocumentFactory.create_batch(2, owner=user2)

doc_admin = DocumentAdmin(model=Document, admin_site=AdminSite())
qs1 = doc_admin.get_queryset(request=Mock(user=user1))

assert qs1.count() == len(u1_private_doc + u1_shared)

qs2 = doc_admin.get_queryset(request=Mock(user=user2))
assert qs2.count() == len(u2_private_doc + u1_shared)
2 changes: 1 addition & 1 deletion openapi_documentor/templates/openapi/document_detail.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% load static %}
{% block custom_javascript %}
<script defer src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/redoc@2.1.0/bundles/redoc.standalone.js"></script>
{% endblock custom_javascript %}

{% block title %}Api: {{ object.title }}{% endblock %}
Expand Down
3 changes: 2 additions & 1 deletion requirements/local.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ ipdb==0.13.5 # https://github.com/gotcha/ipdb

# Testing
# ------------------------------------------------------------------------------
mypy==0.812 # https://github.com/python/mypy
mypy==1.5.1 # https://github.com/python/mypy
django-stubs==1.7.0 # https://github.com/typeddjango/django-stubs
pytest==6.2.2 # https://github.com/pytest-dev/pytest
pytest-sugar==0.9.4 # https://github.com/Frozenball/pytest-sugar
Expand All @@ -32,3 +32,4 @@ django-debug-toolbar==3.2 # https://github.com/jazzband/django-debug-toolbar
django-extensions==3.1.1 # https://github.com/django-extensions/django-extensions
django-coverage-plugin==1.8.0 # https://github.com/nedbat/django_coverage_plugin
pytest-django==4.1.0 # https://github.com/pytest-dev/pytest-django
typed-ast==1.5.4