├── .coverage ├── .gitattributes ├── blog ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-38.pyc │ ├── admin.cpython-38.pyc │ ├── models.cpython-38.pyc │ ├── tests.cpython-38.pyc │ └── urls.cpython-38.pyc ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── __init__.py │ └── __pycache__ │ │ ├── 0001_initial.cpython-38.pyc │ │ └── __init__.cpython-38.pyc ├── models.py ├── tests.py ├── urls.py └── views.py ├── blog_api ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-38.pyc │ ├── admin.cpython-38.pyc │ ├── models.cpython-38.pyc │ ├── serializers.cpython-38.pyc │ ├── tests.cpython-38.pyc │ ├── urls.cpython-38.pyc │ └── views.cpython-38.pyc ├── admin.py ├── apps.py ├── migrations │ ├── __init__.py │ └── __pycache__ │ │ └── __init__.cpython-38.pyc ├── models.py ├── serializers.py ├── tests.py ├── urls.py └── views.py ├── commands.txt ├── core ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-38.pyc │ ├── settings.cpython-38.pyc │ ├── urls.cpython-38.pyc │ └── wsgi.cpython-38.pyc ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py ├── db.sqlite3 ├── htmlcov ├── blog___init___py.html ├── blog_admin_py.html ├── blog_api___init___py.html ├── blog_api_admin_py.html ├── blog_api_migrations___init___py.html ├── blog_api_models_py.html ├── blog_api_serializers_py.html ├── blog_api_tests_py.html ├── blog_api_urls_py.html ├── blog_api_views_py.html ├── blog_migrations_0001_initial_py.html ├── blog_migrations___init___py.html ├── blog_models_py.html ├── blog_tests_py.html ├── blog_urls_py.html ├── core___init___py.html ├── core_settings_py.html ├── core_urls_py.html ├── coverage_html.js ├── index.html ├── jquery.ba-throttle-debounce.min.js ├── jquery.hotkeys.js ├── jquery.isonscreen.js ├── jquery.min.js ├── jquery.tablesorter.min.js ├── keybd_closed.png ├── keybd_open.png ├── manage_py.html ├── status.json └── style.css ├── manage.py └── templates └── blog └── index.html /.coverage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/.coverage -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /blog/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/blog/__init__.py -------------------------------------------------------------------------------- /blog/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/blog/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /blog/__pycache__/admin.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/blog/__pycache__/admin.cpython-38.pyc -------------------------------------------------------------------------------- /blog/__pycache__/models.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/blog/__pycache__/models.cpython-38.pyc -------------------------------------------------------------------------------- /blog/__pycache__/tests.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/blog/__pycache__/tests.cpython-38.pyc -------------------------------------------------------------------------------- /blog/__pycache__/urls.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/blog/__pycache__/urls.cpython-38.pyc -------------------------------------------------------------------------------- /blog/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from . import models 3 | 4 | 5 | @admin.register(models.Post) 6 | class AuthorAdmin(admin.ModelAdmin): 7 | list_display = ('title', 'id', 'status', 'slug', 'author') 8 | prepopulated_fields = {'slug': ('title',), } 9 | 10 | 11 | admin.site.register(models.Category) -------------------------------------------------------------------------------- /blog/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BlogConfig(AppConfig): 5 | name = 'blog' 6 | -------------------------------------------------------------------------------- /blog/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-13 12:10 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | import django.utils.timezone 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Category', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('name', models.CharField(max_length=100)), 23 | ], 24 | ), 25 | migrations.CreateModel( 26 | name='Post', 27 | fields=[ 28 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 29 | ('title', models.CharField(max_length=250)), 30 | ('excerpt', models.TextField(null=True)), 31 | ('content', models.TextField()), 32 | ('slug', models.SlugField(max_length=250, unique_for_date='published')), 33 | ('published', models.DateTimeField(default=django.utils.timezone.now)), 34 | ('status', models.CharField(choices=[('draft', 'Draft'), ('published', 'Published')], default='published', max_length=10)), 35 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='blog_posts', to=settings.AUTH_USER_MODEL)), 36 | ('category', models.ForeignKey(default=1, on_delete=django.db.models.deletion.PROTECT, to='blog.category')), 37 | ], 38 | options={ 39 | 'ordering': ('-published',), 40 | }, 41 | ), 42 | ] 43 | -------------------------------------------------------------------------------- /blog/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/blog/migrations/__init__.py -------------------------------------------------------------------------------- /blog/migrations/__pycache__/0001_initial.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/blog/migrations/__pycache__/0001_initial.cpython-38.pyc -------------------------------------------------------------------------------- /blog/migrations/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/blog/migrations/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /blog/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | from django.utils import timezone 4 | 5 | 6 | class Category(models.Model): 7 | name = models.CharField(max_length=100) 8 | 9 | def __str__(self): 10 | return self.name 11 | 12 | 13 | class Post(models.Model): 14 | 15 | class PostObjects(models.Manager): 16 | def get_queryset(self): 17 | return super().get_queryset() .filter(status='published') 18 | 19 | options = ( 20 | ('draft', 'Draft'), 21 | ('published', 'Published'), 22 | ) 23 | 24 | category = models.ForeignKey( 25 | Category, on_delete=models.PROTECT, default=1) 26 | title = models.CharField(max_length=250) 27 | excerpt = models.TextField(null=True) 28 | content = models.TextField() 29 | slug = models.SlugField(max_length=250, unique_for_date='published') 30 | published = models.DateTimeField(default=timezone.now) 31 | author = models.ForeignKey( 32 | User, on_delete=models.CASCADE, related_name='blog_posts') 33 | status = models.CharField( 34 | max_length=10, choices=options, default='published') 35 | objects = models.Manager() # default manager 36 | postobjects = PostObjects() # custom manager 37 | 38 | class Meta: 39 | ordering = ('-published',) 40 | 41 | def __str__(self): 42 | return self.title 43 | -------------------------------------------------------------------------------- /blog/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.contrib.auth.models import User 3 | from blog.models import Post, Category 4 | 5 | 6 | class Test_Create_Post(TestCase): 7 | 8 | @classmethod 9 | def setUpTestData(cls): 10 | test_category = Category.objects.create(name='django') 11 | testuser1 = User.objects.create_user( 12 | username='test_user1', password='123456789') 13 | test_post = Post.objects.create(category_id=1, title='Post Title', excerpt='Post Excerpt', 14 | content='Post Content', slug='post-title', author_id=1, status='published') 15 | 16 | def test_blog_content(self): 17 | post = Post.postobjects.get(id=1) 18 | cat = Category.objects.get(id=1) 19 | author = f'{post.author}' 20 | excerpt = f'{post.excerpt}' 21 | title = f'{post.title}' 22 | content = f'{post.content}' 23 | status = f'{post.status}' 24 | self.assertEqual(author, 'test_user1') 25 | self.assertEqual(title, 'Post Title') 26 | self.assertEqual(content, 'Post Content') 27 | self.assertEqual(status, 'published') 28 | self.assertEqual(str(post), "Post Title") 29 | self.assertEqual(str(cat), "django") 30 | -------------------------------------------------------------------------------- /blog/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.views.generic import TemplateView 3 | 4 | app_name = 'blog' 5 | 6 | urlpatterns = [ 7 | path('', TemplateView.as_view(template_name="blog/index.html")), 8 | ] -------------------------------------------------------------------------------- /blog/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /blog_api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/blog_api/__init__.py -------------------------------------------------------------------------------- /blog_api/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/blog_api/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /blog_api/__pycache__/admin.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/blog_api/__pycache__/admin.cpython-38.pyc -------------------------------------------------------------------------------- /blog_api/__pycache__/models.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/blog_api/__pycache__/models.cpython-38.pyc -------------------------------------------------------------------------------- /blog_api/__pycache__/serializers.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/blog_api/__pycache__/serializers.cpython-38.pyc -------------------------------------------------------------------------------- /blog_api/__pycache__/tests.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/blog_api/__pycache__/tests.cpython-38.pyc -------------------------------------------------------------------------------- /blog_api/__pycache__/urls.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/blog_api/__pycache__/urls.cpython-38.pyc -------------------------------------------------------------------------------- /blog_api/__pycache__/views.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/blog_api/__pycache__/views.cpython-38.pyc -------------------------------------------------------------------------------- /blog_api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /blog_api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BlogApiConfig(AppConfig): 5 | name = 'blog_api' 6 | -------------------------------------------------------------------------------- /blog_api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/blog_api/migrations/__init__.py -------------------------------------------------------------------------------- /blog_api/migrations/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/blog_api/migrations/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /blog_api/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /blog_api/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from blog.models import Post 3 | 4 | class PostSerializer(serializers.ModelSerializer): 5 | class Meta: 6 | model = Post 7 | fields = ('id', 'title', 'author', 'excerpt', 'content', 'status') -------------------------------------------------------------------------------- /blog_api/tests.py: -------------------------------------------------------------------------------- 1 | from django.urls import reverse 2 | from rest_framework import status 3 | from rest_framework.test import APITestCase 4 | from blog.models import Post, Category 5 | from django.contrib.auth.models import User 6 | from rest_framework.test import APIClient 7 | 8 | 9 | class PostTests(APITestCase): 10 | 11 | def test_view_posts(self): 12 | """ 13 | Ensure we can view all objects. 14 | """ 15 | url = reverse('blog_api:listcreate') 16 | response = self.client.get(url, format='json') 17 | self.assertEqual(response.status_code, status.HTTP_200_OK) 18 | 19 | def test_create_post(self): 20 | """ 21 | Ensure we can create a new Post object and view object. 22 | """ 23 | self.test_category = Category.objects.create(name='django') 24 | self.testuser1 = User.objects.create_superuser( 25 | username='test_user1', password='123456789') 26 | # self.testuser1.is_staff = True 27 | 28 | self.client.login(username=self.testuser1.username, 29 | password='123456789') 30 | 31 | data = {"title": "new", "author": 1, 32 | "excerpt": "new", "content": "new"} 33 | url = reverse('blog_api:listcreate') 34 | response = self.client.post(url, data, format='json') 35 | self.assertEqual(response.status_code, status.HTTP_201_CREATED) 36 | 37 | def test_post_update(self): 38 | 39 | client = APIClient() 40 | 41 | self.test_category = Category.objects.create(name='django') 42 | self.testuser1 = User.objects.create_user( 43 | username='test_user1', password='123456789') 44 | self.testuser2 = User.objects.create_user( 45 | username='test_user2', password='123456789') 46 | test_post = Post.objects.create( 47 | category_id=1, title='Post Title', excerpt='Post Excerpt', content='Post Content', slug='post-title', author_id=1, status='published') 48 | 49 | client.login(username=self.testuser1.username, 50 | password='123456789') 51 | 52 | url = reverse(('blog_api:detailcreate'), kwargs={'pk': 1}) 53 | 54 | response = client.put( 55 | url, { 56 | "title": "New", 57 | "author": 1, 58 | "excerpt": "New", 59 | "content": "New", 60 | "status": "published" 61 | }, format='json') 62 | print(response.data) 63 | self.assertEqual(response.status_code, status.HTTP_200_OK) 64 | -------------------------------------------------------------------------------- /blog_api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import PostList, PostDetail 3 | 4 | app_name = 'blog_api' 5 | 6 | urlpatterns = [ 7 | path('/', PostDetail.as_view(), name='detailcreate'), 8 | path('', PostList.as_view(), name='listcreate'), 9 | ] 10 | -------------------------------------------------------------------------------- /blog_api/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import generics 2 | from blog.models import Post 3 | from .serializers import PostSerializer 4 | from rest_framework.permissions import SAFE_METHODS, IsAuthenticatedOrReadOnly, BasePermission, IsAdminUser, DjangoModelPermissions 5 | 6 | 7 | class PostUserWritePermission(BasePermission): 8 | message = 'Editing posts is restricted to the author only.' 9 | 10 | def has_object_permission(self, request, view, obj): 11 | 12 | if request.method in SAFE_METHODS: 13 | return True 14 | 15 | return obj.author == request.user 16 | 17 | 18 | class PostList(generics.ListCreateAPIView): 19 | permission_classes = [IsAuthenticatedOrReadOnly] 20 | queryset = Post.postobjects.all() 21 | serializer_class = PostSerializer 22 | 23 | 24 | class PostDetail(generics.RetrieveUpdateDestroyAPIView, PostUserWritePermission): 25 | permission_classes = [PostUserWritePermission] 26 | queryset = Post.objects.all() 27 | serializer_class = PostSerializer 28 | 29 | 30 | """ Concrete View Classes 31 | #CreateAPIView 32 | Used for create-only endpoints. 33 | #ListAPIView 34 | Used for read-only endpoints to represent a collection of model instances. 35 | #RetrieveAPIView 36 | Used for read-only endpoints to represent a single model instance. 37 | #DestroyAPIView 38 | Used for delete-only endpoints for a single model instance. 39 | #UpdateAPIView 40 | Used for update-only endpoints for a single model instance. 41 | ##ListCreateAPIView 42 | Used for read-write endpoints to represent a collection of model instances. 43 | RetrieveUpdateAPIView 44 | Used for read or update endpoints to represent a single model instance. 45 | #RetrieveDestroyAPIView 46 | Used for read or delete endpoints to represent a single model instance. 47 | #RetrieveUpdateDestroyAPIView 48 | Used for read-write-delete endpoints to represent a single model instance. 49 | """ 50 | -------------------------------------------------------------------------------- /commands.txt: -------------------------------------------------------------------------------- 1 | py manage.py makemigrations --dry-run --verbosity 3 2 | py manage.py runserver 3 | py manage.py createsuperuser 4 | pip install coverage 5 | coverage run --omit='*/venv/*' manage.py test 6 | coverage html 7 | pip install djangorestframework -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/core/__init__.py -------------------------------------------------------------------------------- /core/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/core/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /core/__pycache__/settings.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/core/__pycache__/settings.cpython-38.pyc -------------------------------------------------------------------------------- /core/__pycache__/urls.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/core/__pycache__/urls.cpython-38.pyc -------------------------------------------------------------------------------- /core/__pycache__/wsgi.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/core/__pycache__/wsgi.cpython-38.pyc -------------------------------------------------------------------------------- /core/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for core project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /core/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for core project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.1.1. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.1/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'apuvicmef^(!j8gx&clu0u(!8m0r^etok^l0)kc!---#(i5=dt' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'blog', 41 | 'blog_api', 42 | 'rest_framework', 43 | 'corsheaders', 44 | ] 45 | 46 | MIDDLEWARE = [ 47 | 'django.middleware.security.SecurityMiddleware', 48 | 'django.contrib.sessions.middleware.SessionMiddleware', 49 | 'corsheaders.middleware.CorsMiddleware', 50 | 'django.middleware.common.CommonMiddleware', 51 | 'django.middleware.csrf.CsrfViewMiddleware', 52 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 53 | 'django.contrib.messages.middleware.MessageMiddleware', 54 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 55 | ] 56 | 57 | ROOT_URLCONF = 'core.urls' 58 | 59 | TEMPLATES = [ 60 | { 61 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 62 | 'DIRS': [BASE_DIR / 'templates'], 63 | 'APP_DIRS': True, 64 | 'OPTIONS': { 65 | 'context_processors': [ 66 | 'django.template.context_processors.debug', 67 | 'django.template.context_processors.request', 68 | 'django.contrib.auth.context_processors.auth', 69 | 'django.contrib.messages.context_processors.messages', 70 | ], 71 | }, 72 | }, 73 | ] 74 | 75 | WSGI_APPLICATION = 'core.wsgi.application' 76 | 77 | 78 | # Database 79 | # https://docs.djangoproject.com/en/3.1/ref/settings/#databases 80 | 81 | DATABASES = { 82 | 'default': { 83 | 'ENGINE': 'django.db.backends.sqlite3', 84 | 'NAME': BASE_DIR / 'db.sqlite3', 85 | } 86 | } 87 | 88 | 89 | # Password validation 90 | # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators 91 | 92 | AUTH_PASSWORD_VALIDATORS = [ 93 | { 94 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 95 | }, 96 | { 97 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 98 | }, 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 101 | }, 102 | { 103 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 104 | }, 105 | ] 106 | 107 | 108 | # Internationalization 109 | # https://docs.djangoproject.com/en/3.1/topics/i18n/ 110 | 111 | LANGUAGE_CODE = 'en-us' 112 | 113 | TIME_ZONE = 'UTC' 114 | 115 | USE_I18N = True 116 | 117 | USE_L10N = True 118 | 119 | USE_TZ = True 120 | 121 | 122 | # Static files (CSS, JavaScript, Images) 123 | # https://docs.djangoproject.com/en/3.1/howto/static-files/ 124 | 125 | STATIC_URL = '/static/' 126 | 127 | REST_FRAMEWORK = { 128 | 'DEFAULT_PERMISSION_CLASSES': [ 129 | 'rest_framework.permissions.IsAuthenticatedOrReadOnly', 130 | ] 131 | } 132 | 133 | # Permissions: 134 | # AllowAny 135 | # IsAuthenticated 136 | # IsAdminUser 137 | # IsAuthenticatedOrReadOnly 138 | 139 | CORS_ALLOWED_ORIGINS = [ 140 | "http://localhost:3000" 141 | ] 142 | -------------------------------------------------------------------------------- /core/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | 4 | urlpatterns = [ 5 | path('admin/', admin.site.urls), 6 | path('', include('blog.urls', namespace='blog')), 7 | path('api/', include('blog_api.urls', namespace='blog_api')), 8 | path('api-auth/', include('rest_framework.urls', namespace='rest_framework')) 9 | ] 10 | -------------------------------------------------------------------------------- /core/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for core project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-Permissions-Part-2/5e70ee13d5caefbb4dd78971c9b23777db88f68e/db.sqlite3 -------------------------------------------------------------------------------- /htmlcov/blog___init___py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog\__init__.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |
55 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /htmlcov/blog_admin_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog\admin.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1from django.contrib import admin 

55 |

2from . import models 

56 |

3 

57 |

4 

58 |

5@admin.register(models.Post) 

59 |

6class AuthorAdmin(admin.ModelAdmin): 

60 |

7 list_display = ('title', 'id', 'status', 'slug', 'author') 

61 |

8 prepopulated_fields = {'slug': ('title',), } 

62 |

9 

63 |

10 

64 |

11admin.site.register(models.Category) 

65 |
66 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /htmlcov/blog_api___init___py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog_api\__init__.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |
55 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /htmlcov/blog_api_admin_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog_api\admin.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1from django.contrib import admin 

55 |

2 

56 |

3# Register your models here. 

57 |
58 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /htmlcov/blog_api_migrations___init___py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog_api\migrations\__init__.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |
55 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /htmlcov/blog_api_models_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog_api\models.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1from django.db import models 

55 |

2 

56 |

3# Create your models here. 

57 |
58 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /htmlcov/blog_api_serializers_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog_api\serializers.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1from rest_framework import serializers 

55 |

2from blog.models import Post 

56 |

3 

57 |

4class PostSerializer(serializers.ModelSerializer): 

58 |

5 class Meta: 

59 |

6 model = Post 

60 |

7 fields = ('id', 'title', 'author', 'excerpt', 'content', 'status') 

61 |
62 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /htmlcov/blog_api_tests_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog_api\tests.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1from django.test import TestCase 

55 |

2 

56 |

3# Create your tests here. 

57 |
58 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /htmlcov/blog_api_urls_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog_api\urls.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1from django.urls import path 

55 |

2from .views import PostList, PostDetail 

56 |

3 

57 |

4app_name = 'blog_api' 

58 |

5 

59 |

6urlpatterns = [ 

60 |

7 path('<int:pk>/', PostDetail.as_view(), name='detailcreate'), 

61 |

8 path('', PostList.as_view(), name='listcreate'), 

62 |

9] 

63 |
64 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /htmlcov/blog_api_views_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog_api\views.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1from rest_framework import generics 

55 |

2from blog.models import Post 

56 |

3from .serializers import PostSerializer 

57 |

4 

58 |

5 

59 |

6class PostList(generics.ListCreateAPIView): 

60 |

7 queryset = Post.postobjects.all() 

61 |

8 serializer_class = PostSerializer 

62 |

9 

63 |

10 

64 |

11class PostDetail(generics.RetrieveAPIView): 

65 |

12 queryset = Post.objects.all() 

66 |

13 serializer_class = PostSerializer 

67 |

14 

68 |

15 

69 |

16""" Concrete View Classes 

70 |

17#CreateAPIView 

71 |

18Used for create-only endpoints. 

72 |

19#ListAPIView 

73 |

20Used for read-only endpoints to represent a collection of model instances. 

74 |

21#RetrieveAPIView 

75 |

22Used for read-only endpoints to represent a single model instance. 

76 |

23#DestroyAPIView 

77 |

24Used for delete-only endpoints for a single model instance. 

78 |

25#UpdateAPIView 

79 |

26Used for update-only endpoints for a single model instance. 

80 |

27##ListCreateAPIView 

81 |

28Used for read-write endpoints to represent a collection of model instances. 

82 |

29RetrieveUpdateAPIView 

83 |

30Used for read or update endpoints to represent a single model instance. 

84 |

31#RetrieveDestroyAPIView 

85 |

32Used for read or delete endpoints to represent a single model instance. 

86 |

33#RetrieveUpdateDestroyAPIView 

87 |

34Used for read-write-delete endpoints to represent a single model instance. 

88 |

35""" 

89 |
90 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /htmlcov/blog_migrations_0001_initial_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog\migrations\0001_initial.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1# Generated by Django 3.1.1 on 2020-09-13 12:10 

55 |

2 

56 |

3from django.conf import settings 

57 |

4from django.db import migrations, models 

58 |

5import django.db.models.deletion 

59 |

6import django.utils.timezone 

60 |

7 

61 |

8 

62 |

9class Migration(migrations.Migration): 

63 |

10 

64 |

11 initial = True 

65 |

12 

66 |

13 dependencies = [ 

67 |

14 migrations.swappable_dependency(settings.AUTH_USER_MODEL), 

68 |

15 ] 

69 |

16 

70 |

17 operations = [ 

71 |

18 migrations.CreateModel( 

72 |

19 name='Category', 

73 |

20 fields=[ 

74 |

21 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 

75 |

22 ('name', models.CharField(max_length=100)), 

76 |

23 ], 

77 |

24 ), 

78 |

25 migrations.CreateModel( 

79 |

26 name='Post', 

80 |

27 fields=[ 

81 |

28 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 

82 |

29 ('title', models.CharField(max_length=250)), 

83 |

30 ('excerpt', models.TextField(null=True)), 

84 |

31 ('content', models.TextField()), 

85 |

32 ('slug', models.SlugField(max_length=250, unique_for_date='published')), 

86 |

33 ('published', models.DateTimeField(default=django.utils.timezone.now)), 

87 |

34 ('status', models.CharField(choices=[('draft', 'Draft'), ('published', 'Published')], default='published', max_length=10)), 

88 |

35 ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='blog_posts', to=settings.AUTH_USER_MODEL)), 

89 |

36 ('category', models.ForeignKey(default=1, on_delete=django.db.models.deletion.PROTECT, to='blog.category')), 

90 |

37 ], 

91 |

38 options={ 

92 |

39 'ordering': ('-published',), 

93 |

40 }, 

94 |

41 ), 

95 |

42 ] 

96 |
97 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /htmlcov/blog_migrations___init___py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog\migrations\__init__.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |
55 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /htmlcov/blog_models_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog\models.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1from django.db import models 

55 |

2from django.contrib.auth.models import User 

56 |

3from django.utils import timezone 

57 |

4 

58 |

5 

59 |

6class Category(models.Model): 

60 |

7 name = models.CharField(max_length=100) 

61 |

8 

62 |

9 def __str__(self): 

63 |

10 return self.name 

64 |

11 

65 |

12 

66 |

13class Post(models.Model): 

67 |

14 

68 |

15 class PostObjects(models.Manager): 

69 |

16 def get_queryset(self): 

70 |

17 return super().get_queryset() .filter(status='published') 

71 |

18 

72 |

19 options = ( 

73 |

20 ('draft', 'Draft'), 

74 |

21 ('published', 'Published'), 

75 |

22 ) 

76 |

23 

77 |

24 category = models.ForeignKey( 

78 |

25 Category, on_delete=models.PROTECT, default=1) 

79 |

26 title = models.CharField(max_length=250) 

80 |

27 excerpt = models.TextField(null=True) 

81 |

28 content = models.TextField() 

82 |

29 slug = models.SlugField(max_length=250, unique_for_date='published') 

83 |

30 published = models.DateTimeField(default=timezone.now) 

84 |

31 author = models.ForeignKey( 

85 |

32 User, on_delete=models.CASCADE, related_name='blog_posts') 

86 |

33 status = models.CharField( 

87 |

34 max_length=10, choices=options, default='published') 

88 |

35 objects = models.Manager() # default manager 

89 |

36 postobjects = PostObjects() # custom manager 

90 |

37 

91 |

38 class Meta: 

92 |

39 ordering = ('-published',) 

93 |

40 

94 |

41 def __str__(self): 

95 |

42 return self.title 

96 |
97 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /htmlcov/blog_tests_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog\tests.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1from django.test import TestCase 

55 |

2from django.contrib.auth.models import User 

56 |

3from blog.models import Post, Category 

57 |

4 

58 |

5 

59 |

6class Test_Create_Post(TestCase): 

60 |

7 

61 |

8 @classmethod 

62 |

9 def setUpTestData(cls): 

63 |

10 test_category = Category.objects.create(name='django') 

64 |

11 testuser1 = User.objects.create_user( 

65 |

12 username='test_user1', password='123456789') 

66 |

13 test_post = Post.objects.create(category_id=1, title='Post Title', excerpt='Post Excerpt', 

67 |

14 content='Post Content', slug='post-title', author_id=1, status='published') 

68 |

15 

69 |

16 def test_blog_content(self): 

70 |

17 post = Post.postobjects.get(id=1) 

71 |

18 cat = Category.objects.get(id=1) 

72 |

19 author = f'{post.author}' 

73 |

20 excerpt = f'{post.excerpt}' 

74 |

21 title = f'{post.title}' 

75 |

22 content = f'{post.content}' 

76 |

23 status = f'{post.status}' 

77 |

24 self.assertEqual(author, 'test_user1') 

78 |

25 self.assertEqual(title, 'Post Title') 

79 |

26 self.assertEqual(content, 'Post Content') 

80 |

27 self.assertEqual(status, 'published') 

81 |

28 self.assertEqual(str(post), "Post Title") 

82 |

29 self.assertEqual(str(cat), "django") 

83 |
84 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /htmlcov/blog_urls_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog\urls.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1from django.urls import path 

55 |

2from django.views.generic import TemplateView 

56 |

3 

57 |

4app_name = 'blog' 

58 |

5 

59 |

6urlpatterns = [ 

60 |

7 path('', TemplateView.as_view(template_name="blog/index.html")), 

61 |

8] 

62 |
63 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /htmlcov/core___init___py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for core\__init__.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |
55 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /htmlcov/core_settings_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for core\settings.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1""" 

55 |

2Django settings for core project. 

56 |

3 

57 |

4Generated by 'django-admin startproject' using Django 3.1.1. 

58 |

5 

59 |

6For more information on this file, see 

60 |

7https://docs.djangoproject.com/en/3.1/topics/settings/ 

61 |

8 

62 |

9For the full list of settings and their values, see 

63 |

10https://docs.djangoproject.com/en/3.1/ref/settings/ 

64 |

11""" 

65 |

12 

66 |

13from pathlib import Path 

67 |

14 

68 |

15# Build paths inside the project like this: BASE_DIR / 'subdir'. 

69 |

16BASE_DIR = Path(__file__).resolve().parent.parent 

70 |

17 

71 |

18 

72 |

19# Quick-start development settings - unsuitable for production 

73 |

20# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ 

74 |

21 

75 |

22# SECURITY WARNING: keep the secret key used in production secret! 

76 |

23SECRET_KEY = 'apuvicmef^(!j8gx&clu0u(!8m0r^etok^l0)kc!---#(i5=dt' 

77 |

24 

78 |

25# SECURITY WARNING: don't run with debug turned on in production! 

79 |

26DEBUG = True 

80 |

27 

81 |

28ALLOWED_HOSTS = [] 

82 |

29 

83 |

30 

84 |

31# Application definition 

85 |

32 

86 |

33INSTALLED_APPS = [ 

87 |

34 'django.contrib.admin', 

88 |

35 'django.contrib.auth', 

89 |

36 'django.contrib.contenttypes', 

90 |

37 'django.contrib.sessions', 

91 |

38 'django.contrib.messages', 

92 |

39 'django.contrib.staticfiles', 

93 |

40 'blog', 

94 |

41 'blog_api', 

95 |

42 'rest_framework', 

96 |

43] 

97 |

44 

98 |

45MIDDLEWARE = [ 

99 |

46 'django.middleware.security.SecurityMiddleware', 

100 |

47 'django.contrib.sessions.middleware.SessionMiddleware', 

101 |

48 'django.middleware.common.CommonMiddleware', 

102 |

49 'django.middleware.csrf.CsrfViewMiddleware', 

103 |

50 'django.contrib.auth.middleware.AuthenticationMiddleware', 

104 |

51 'django.contrib.messages.middleware.MessageMiddleware', 

105 |

52 'django.middleware.clickjacking.XFrameOptionsMiddleware', 

106 |

53] 

107 |

54 

108 |

55ROOT_URLCONF = 'core.urls' 

109 |

56 

110 |

57TEMPLATES = [ 

111 |

58 { 

112 |

59 'BACKEND': 'django.template.backends.django.DjangoTemplates', 

113 |

60 'DIRS': [BASE_DIR / 'templates'], 

114 |

61 'APP_DIRS': True, 

115 |

62 'OPTIONS': { 

116 |

63 'context_processors': [ 

117 |

64 'django.template.context_processors.debug', 

118 |

65 'django.template.context_processors.request', 

119 |

66 'django.contrib.auth.context_processors.auth', 

120 |

67 'django.contrib.messages.context_processors.messages', 

121 |

68 ], 

122 |

69 }, 

123 |

70 }, 

124 |

71] 

125 |

72 

126 |

73WSGI_APPLICATION = 'core.wsgi.application' 

127 |

74 

128 |

75 

129 |

76# Database 

130 |

77# https://docs.djangoproject.com/en/3.1/ref/settings/#databases 

131 |

78 

132 |

79DATABASES = { 

133 |

80 'default': { 

134 |

81 'ENGINE': 'django.db.backends.sqlite3', 

135 |

82 'NAME': BASE_DIR / 'db.sqlite3', 

136 |

83 } 

137 |

84} 

138 |

85 

139 |

86 

140 |

87# Password validation 

141 |

88# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators 

142 |

89 

143 |

90AUTH_PASSWORD_VALIDATORS = [ 

144 |

91 { 

145 |

92 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 

146 |

93 }, 

147 |

94 { 

148 |

95 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 

149 |

96 }, 

150 |

97 { 

151 |

98 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 

152 |

99 }, 

153 |

100 { 

154 |

101 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 

155 |

102 }, 

156 |

103] 

157 |

104 

158 |

105 

159 |

106# Internationalization 

160 |

107# https://docs.djangoproject.com/en/3.1/topics/i18n/ 

161 |

108 

162 |

109LANGUAGE_CODE = 'en-us' 

163 |

110 

164 |

111TIME_ZONE = 'UTC' 

165 |

112 

166 |

113USE_I18N = True 

167 |

114 

168 |

115USE_L10N = True 

169 |

116 

170 |

117USE_TZ = True 

171 |

118 

172 |

119 

173 |

120# Static files (CSS, JavaScript, Images) 

174 |

121# https://docs.djangoproject.com/en/3.1/howto/static-files/ 

175 |

122 

176 |

123STATIC_URL = '/static/' 

177 |

124 

178 |

125REST_FRAMEWORK = { 

179 |

126 'DEFAULT_PERMISSION_CLASSES': [ 

180 |

127 'rest_framework.permissions.AllowAny', 

181 |

128 ] 

182 |

129} 

183 |
184 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /htmlcov/core_urls_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for core\urls.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1from django.contrib import admin 

55 |

2from django.urls import path, include 

56 |

3 

57 |

4urlpatterns = [ 

58 |

5 path('admin/', admin.site.urls), 

59 |

6 path('', include('blog.urls', namespace='blog')), 

60 |

7 path('api/', include('blog_api.urls', namespace='blog_api')), 

61 |

8] 

62 |
63 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /htmlcov/coverage_html.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | // For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | // Coverage.py HTML report browser code. 5 | /*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */ 6 | /*global coverage: true, document, window, $ */ 7 | 8 | coverage = {}; 9 | 10 | // Find all the elements with shortkey_* class, and use them to assign a shortcut key. 11 | coverage.assign_shortkeys = function () { 12 | $("*[class*='shortkey_']").each(function (i, e) { 13 | $.each($(e).attr("class").split(" "), function (i, c) { 14 | if (/^shortkey_/.test(c)) { 15 | $(document).bind('keydown', c.substr(9), function () { 16 | $(e).click(); 17 | }); 18 | } 19 | }); 20 | }); 21 | }; 22 | 23 | // Create the events for the help panel. 24 | coverage.wire_up_help_panel = function () { 25 | $("#keyboard_icon").click(function () { 26 | // Show the help panel, and position it so the keyboard icon in the 27 | // panel is in the same place as the keyboard icon in the header. 28 | $(".help_panel").show(); 29 | var koff = $("#keyboard_icon").offset(); 30 | var poff = $("#panel_icon").position(); 31 | $(".help_panel").offset({ 32 | top: koff.top-poff.top, 33 | left: koff.left-poff.left 34 | }); 35 | }); 36 | $("#panel_icon").click(function () { 37 | $(".help_panel").hide(); 38 | }); 39 | }; 40 | 41 | // Create the events for the filter box. 42 | coverage.wire_up_filter = function () { 43 | // Cache elements. 44 | var table = $("table.index"); 45 | var table_rows = table.find("tbody tr"); 46 | var table_row_names = table_rows.find("td.name a"); 47 | var no_rows = $("#no_rows"); 48 | 49 | // Create a duplicate table footer that we can modify with dynamic summed values. 50 | var table_footer = $("table.index tfoot tr"); 51 | var table_dynamic_footer = table_footer.clone(); 52 | table_dynamic_footer.attr('class', 'total_dynamic hidden'); 53 | table_footer.after(table_dynamic_footer); 54 | 55 | // Observe filter keyevents. 56 | $("#filter").on("keyup change", $.debounce(150, function (event) { 57 | var filter_value = $(this).val(); 58 | 59 | if (filter_value === "") { 60 | // Filter box is empty, remove all filtering. 61 | table_rows.removeClass("hidden"); 62 | 63 | // Show standard footer, hide dynamic footer. 64 | table_footer.removeClass("hidden"); 65 | table_dynamic_footer.addClass("hidden"); 66 | 67 | // Hide placeholder, show table. 68 | if (no_rows.length > 0) { 69 | no_rows.hide(); 70 | } 71 | table.show(); 72 | 73 | } 74 | else { 75 | // Filter table items by value. 76 | var hidden = 0; 77 | var shown = 0; 78 | 79 | // Hide / show elements. 80 | $.each(table_row_names, function () { 81 | var element = $(this).parents("tr"); 82 | 83 | if ($(this).text().indexOf(filter_value) === -1) { 84 | // hide 85 | element.addClass("hidden"); 86 | hidden++; 87 | } 88 | else { 89 | // show 90 | element.removeClass("hidden"); 91 | shown++; 92 | } 93 | }); 94 | 95 | // Show placeholder if no rows will be displayed. 96 | if (no_rows.length > 0) { 97 | if (shown === 0) { 98 | // Show placeholder, hide table. 99 | no_rows.show(); 100 | table.hide(); 101 | } 102 | else { 103 | // Hide placeholder, show table. 104 | no_rows.hide(); 105 | table.show(); 106 | } 107 | } 108 | 109 | // Manage dynamic header: 110 | if (hidden > 0) { 111 | // Calculate new dynamic sum values based on visible rows. 112 | for (var column = 2; column < 20; column++) { 113 | // Calculate summed value. 114 | var cells = table_rows.find('td:nth-child(' + column + ')'); 115 | if (!cells.length) { 116 | // No more columns...! 117 | break; 118 | } 119 | 120 | var sum = 0, numer = 0, denom = 0; 121 | $.each(cells.filter(':visible'), function () { 122 | var ratio = $(this).data("ratio"); 123 | if (ratio) { 124 | var splitted = ratio.split(" "); 125 | numer += parseInt(splitted[0], 10); 126 | denom += parseInt(splitted[1], 10); 127 | } 128 | else { 129 | sum += parseInt(this.innerHTML, 10); 130 | } 131 | }); 132 | 133 | // Get footer cell element. 134 | var footer_cell = table_dynamic_footer.find('td:nth-child(' + column + ')'); 135 | 136 | // Set value into dynamic footer cell element. 137 | if (cells[0].innerHTML.indexOf('%') > -1) { 138 | // Percentage columns use the numerator and denominator, 139 | // and adapt to the number of decimal places. 140 | var match = /\.([0-9]+)/.exec(cells[0].innerHTML); 141 | var places = 0; 142 | if (match) { 143 | places = match[1].length; 144 | } 145 | var pct = numer * 100 / denom; 146 | footer_cell.text(pct.toFixed(places) + '%'); 147 | } 148 | else { 149 | footer_cell.text(sum); 150 | } 151 | } 152 | 153 | // Hide standard footer, show dynamic footer. 154 | table_footer.addClass("hidden"); 155 | table_dynamic_footer.removeClass("hidden"); 156 | } 157 | else { 158 | // Show standard footer, hide dynamic footer. 159 | table_footer.removeClass("hidden"); 160 | table_dynamic_footer.addClass("hidden"); 161 | } 162 | } 163 | })); 164 | 165 | // Trigger change event on setup, to force filter on page refresh 166 | // (filter value may still be present). 167 | $("#filter").trigger("change"); 168 | }; 169 | 170 | // Loaded on index.html 171 | coverage.index_ready = function ($) { 172 | // Look for a localStorage item containing previous sort settings: 173 | var sort_list = []; 174 | var storage_name = "COVERAGE_INDEX_SORT"; 175 | var stored_list = undefined; 176 | try { 177 | stored_list = localStorage.getItem(storage_name); 178 | } catch(err) {} 179 | 180 | if (stored_list) { 181 | sort_list = JSON.parse('[[' + stored_list + ']]'); 182 | } 183 | 184 | // Create a new widget which exists only to save and restore 185 | // the sort order: 186 | $.tablesorter.addWidget({ 187 | id: "persistentSort", 188 | 189 | // Format is called by the widget before displaying: 190 | format: function (table) { 191 | if (table.config.sortList.length === 0 && sort_list.length > 0) { 192 | // This table hasn't been sorted before - we'll use 193 | // our stored settings: 194 | $(table).trigger('sorton', [sort_list]); 195 | } 196 | else { 197 | // This is not the first load - something has 198 | // already defined sorting so we'll just update 199 | // our stored value to match: 200 | sort_list = table.config.sortList; 201 | } 202 | } 203 | }); 204 | 205 | // Configure our tablesorter to handle the variable number of 206 | // columns produced depending on report options: 207 | var headers = []; 208 | var col_count = $("table.index > thead > tr > th").length; 209 | 210 | headers[0] = { sorter: 'text' }; 211 | for (i = 1; i < col_count-1; i++) { 212 | headers[i] = { sorter: 'digit' }; 213 | } 214 | headers[col_count-1] = { sorter: 'percent' }; 215 | 216 | // Enable the table sorter: 217 | $("table.index").tablesorter({ 218 | widgets: ['persistentSort'], 219 | headers: headers 220 | }); 221 | 222 | coverage.assign_shortkeys(); 223 | coverage.wire_up_help_panel(); 224 | coverage.wire_up_filter(); 225 | 226 | // Watch for page unload events so we can save the final sort settings: 227 | $(window).unload(function () { 228 | try { 229 | localStorage.setItem(storage_name, sort_list.toString()) 230 | } catch(err) {} 231 | }); 232 | }; 233 | 234 | // -- pyfile stuff -- 235 | 236 | coverage.pyfile_ready = function ($) { 237 | // If we're directed to a particular line number, highlight the line. 238 | var frag = location.hash; 239 | if (frag.length > 2 && frag[1] === 't') { 240 | $(frag).addClass('highlight'); 241 | coverage.set_sel(parseInt(frag.substr(2), 10)); 242 | } 243 | else { 244 | coverage.set_sel(0); 245 | } 246 | 247 | $(document) 248 | .bind('keydown', 'j', coverage.to_next_chunk_nicely) 249 | .bind('keydown', 'k', coverage.to_prev_chunk_nicely) 250 | .bind('keydown', '0', coverage.to_top) 251 | .bind('keydown', '1', coverage.to_first_chunk) 252 | ; 253 | 254 | $(".button_toggle_run").click(function (evt) {coverage.toggle_lines(evt.target, "run");}); 255 | $(".button_toggle_exc").click(function (evt) {coverage.toggle_lines(evt.target, "exc");}); 256 | $(".button_toggle_mis").click(function (evt) {coverage.toggle_lines(evt.target, "mis");}); 257 | $(".button_toggle_par").click(function (evt) {coverage.toggle_lines(evt.target, "par");}); 258 | 259 | coverage.assign_shortkeys(); 260 | coverage.wire_up_help_panel(); 261 | 262 | coverage.init_scroll_markers(); 263 | 264 | // Rebuild scroll markers when the window height changes. 265 | $(window).resize(coverage.build_scroll_markers); 266 | }; 267 | 268 | coverage.toggle_lines = function (btn, cls) { 269 | btn = $(btn); 270 | var show = "show_"+cls; 271 | if (btn.hasClass(show)) { 272 | $("#source ." + cls).removeClass(show); 273 | btn.removeClass(show); 274 | } 275 | else { 276 | $("#source ." + cls).addClass(show); 277 | btn.addClass(show); 278 | } 279 | coverage.build_scroll_markers(); 280 | }; 281 | 282 | // Return the nth line div. 283 | coverage.line_elt = function (n) { 284 | return $("#t" + n); 285 | }; 286 | 287 | // Return the nth line number div. 288 | coverage.num_elt = function (n) { 289 | return $("#n" + n); 290 | }; 291 | 292 | // Set the selection. b and e are line numbers. 293 | coverage.set_sel = function (b, e) { 294 | // The first line selected. 295 | coverage.sel_begin = b; 296 | // The next line not selected. 297 | coverage.sel_end = (e === undefined) ? b+1 : e; 298 | }; 299 | 300 | coverage.to_top = function () { 301 | coverage.set_sel(0, 1); 302 | coverage.scroll_window(0); 303 | }; 304 | 305 | coverage.to_first_chunk = function () { 306 | coverage.set_sel(0, 1); 307 | coverage.to_next_chunk(); 308 | }; 309 | 310 | // Return a string indicating what kind of chunk this line belongs to, 311 | // or null if not a chunk. 312 | coverage.chunk_indicator = function (line_elt) { 313 | var klass = line_elt.attr('class'); 314 | if (klass) { 315 | var m = klass.match(/\bshow_\w+\b/); 316 | if (m) { 317 | return m[0]; 318 | } 319 | } 320 | return null; 321 | }; 322 | 323 | coverage.to_next_chunk = function () { 324 | var c = coverage; 325 | 326 | // Find the start of the next colored chunk. 327 | var probe = c.sel_end; 328 | var chunk_indicator, probe_line; 329 | while (true) { 330 | probe_line = c.line_elt(probe); 331 | if (probe_line.length === 0) { 332 | return; 333 | } 334 | chunk_indicator = c.chunk_indicator(probe_line); 335 | if (chunk_indicator) { 336 | break; 337 | } 338 | probe++; 339 | } 340 | 341 | // There's a next chunk, `probe` points to it. 342 | var begin = probe; 343 | 344 | // Find the end of this chunk. 345 | var next_indicator = chunk_indicator; 346 | while (next_indicator === chunk_indicator) { 347 | probe++; 348 | probe_line = c.line_elt(probe); 349 | next_indicator = c.chunk_indicator(probe_line); 350 | } 351 | c.set_sel(begin, probe); 352 | c.show_selection(); 353 | }; 354 | 355 | coverage.to_prev_chunk = function () { 356 | var c = coverage; 357 | 358 | // Find the end of the prev colored chunk. 359 | var probe = c.sel_begin-1; 360 | var probe_line = c.line_elt(probe); 361 | if (probe_line.length === 0) { 362 | return; 363 | } 364 | var chunk_indicator = c.chunk_indicator(probe_line); 365 | while (probe > 0 && !chunk_indicator) { 366 | probe--; 367 | probe_line = c.line_elt(probe); 368 | if (probe_line.length === 0) { 369 | return; 370 | } 371 | chunk_indicator = c.chunk_indicator(probe_line); 372 | } 373 | 374 | // There's a prev chunk, `probe` points to its last line. 375 | var end = probe+1; 376 | 377 | // Find the beginning of this chunk. 378 | var prev_indicator = chunk_indicator; 379 | while (prev_indicator === chunk_indicator) { 380 | probe--; 381 | probe_line = c.line_elt(probe); 382 | prev_indicator = c.chunk_indicator(probe_line); 383 | } 384 | c.set_sel(probe+1, end); 385 | c.show_selection(); 386 | }; 387 | 388 | // Return the line number of the line nearest pixel position pos 389 | coverage.line_at_pos = function (pos) { 390 | var l1 = coverage.line_elt(1), 391 | l2 = coverage.line_elt(2), 392 | result; 393 | if (l1.length && l2.length) { 394 | var l1_top = l1.offset().top, 395 | line_height = l2.offset().top - l1_top, 396 | nlines = (pos - l1_top) / line_height; 397 | if (nlines < 1) { 398 | result = 1; 399 | } 400 | else { 401 | result = Math.ceil(nlines); 402 | } 403 | } 404 | else { 405 | result = 1; 406 | } 407 | return result; 408 | }; 409 | 410 | // Returns 0, 1, or 2: how many of the two ends of the selection are on 411 | // the screen right now? 412 | coverage.selection_ends_on_screen = function () { 413 | if (coverage.sel_begin === 0) { 414 | return 0; 415 | } 416 | 417 | var top = coverage.line_elt(coverage.sel_begin); 418 | var next = coverage.line_elt(coverage.sel_end-1); 419 | 420 | return ( 421 | (top.isOnScreen() ? 1 : 0) + 422 | (next.isOnScreen() ? 1 : 0) 423 | ); 424 | }; 425 | 426 | coverage.to_next_chunk_nicely = function () { 427 | coverage.finish_scrolling(); 428 | if (coverage.selection_ends_on_screen() === 0) { 429 | // The selection is entirely off the screen: select the top line on 430 | // the screen. 431 | var win = $(window); 432 | coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop())); 433 | } 434 | coverage.to_next_chunk(); 435 | }; 436 | 437 | coverage.to_prev_chunk_nicely = function () { 438 | coverage.finish_scrolling(); 439 | if (coverage.selection_ends_on_screen() === 0) { 440 | var win = $(window); 441 | coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop() + win.height())); 442 | } 443 | coverage.to_prev_chunk(); 444 | }; 445 | 446 | // Select line number lineno, or if it is in a colored chunk, select the 447 | // entire chunk 448 | coverage.select_line_or_chunk = function (lineno) { 449 | var c = coverage; 450 | var probe_line = c.line_elt(lineno); 451 | if (probe_line.length === 0) { 452 | return; 453 | } 454 | var the_indicator = c.chunk_indicator(probe_line); 455 | if (the_indicator) { 456 | // The line is in a highlighted chunk. 457 | // Search backward for the first line. 458 | var probe = lineno; 459 | var indicator = the_indicator; 460 | while (probe > 0 && indicator === the_indicator) { 461 | probe--; 462 | probe_line = c.line_elt(probe); 463 | if (probe_line.length === 0) { 464 | break; 465 | } 466 | indicator = c.chunk_indicator(probe_line); 467 | } 468 | var begin = probe + 1; 469 | 470 | // Search forward for the last line. 471 | probe = lineno; 472 | indicator = the_indicator; 473 | while (indicator === the_indicator) { 474 | probe++; 475 | probe_line = c.line_elt(probe); 476 | indicator = c.chunk_indicator(probe_line); 477 | } 478 | 479 | coverage.set_sel(begin, probe); 480 | } 481 | else { 482 | coverage.set_sel(lineno); 483 | } 484 | }; 485 | 486 | coverage.show_selection = function () { 487 | var c = coverage; 488 | 489 | // Highlight the lines in the chunk 490 | $(".linenos .highlight").removeClass("highlight"); 491 | for (var probe = c.sel_begin; probe > 0 && probe < c.sel_end; probe++) { 492 | c.num_elt(probe).addClass("highlight"); 493 | } 494 | 495 | c.scroll_to_selection(); 496 | }; 497 | 498 | coverage.scroll_to_selection = function () { 499 | // Scroll the page if the chunk isn't fully visible. 500 | if (coverage.selection_ends_on_screen() < 2) { 501 | // Need to move the page. The html,body trick makes it scroll in all 502 | // browsers, got it from http://stackoverflow.com/questions/3042651 503 | var top = coverage.line_elt(coverage.sel_begin); 504 | var top_pos = parseInt(top.offset().top, 10); 505 | coverage.scroll_window(top_pos - 30); 506 | } 507 | }; 508 | 509 | coverage.scroll_window = function (to_pos) { 510 | $("html,body").animate({scrollTop: to_pos}, 200); 511 | }; 512 | 513 | coverage.finish_scrolling = function () { 514 | $("html,body").stop(true, true); 515 | }; 516 | 517 | coverage.init_scroll_markers = function () { 518 | var c = coverage; 519 | // Init some variables 520 | c.lines_len = $('#source p').length; 521 | c.body_h = $('body').height(); 522 | c.header_h = $('div#header').height(); 523 | 524 | // Build html 525 | c.build_scroll_markers(); 526 | }; 527 | 528 | coverage.build_scroll_markers = function () { 529 | var c = coverage, 530 | min_line_height = 3, 531 | max_line_height = 10, 532 | visible_window_h = $(window).height(); 533 | 534 | c.lines_to_mark = $('#source').find('p.show_run, p.show_mis, p.show_exc, p.show_exc, p.show_par'); 535 | $('#scroll_marker').remove(); 536 | // Don't build markers if the window has no scroll bar. 537 | if (c.body_h <= visible_window_h) { 538 | return; 539 | } 540 | 541 | $("body").append("
 
"); 542 | var scroll_marker = $('#scroll_marker'), 543 | marker_scale = scroll_marker.height() / c.body_h, 544 | line_height = scroll_marker.height() / c.lines_len; 545 | 546 | // Line height must be between the extremes. 547 | if (line_height > min_line_height) { 548 | if (line_height > max_line_height) { 549 | line_height = max_line_height; 550 | } 551 | } 552 | else { 553 | line_height = min_line_height; 554 | } 555 | 556 | var previous_line = -99, 557 | last_mark, 558 | last_top, 559 | offsets = {}; 560 | 561 | // Calculate line offsets outside loop to prevent relayouts 562 | c.lines_to_mark.each(function() { 563 | offsets[this.id] = $(this).offset().top; 564 | }); 565 | c.lines_to_mark.each(function () { 566 | var id_name = $(this).attr('id'), 567 | line_top = Math.round(offsets[id_name] * marker_scale), 568 | line_number = parseInt(id_name.substring(1, id_name.length)); 569 | 570 | if (line_number === previous_line + 1) { 571 | // If this solid missed block just make previous mark higher. 572 | last_mark.css({ 573 | 'height': line_top + line_height - last_top 574 | }); 575 | } 576 | else { 577 | // Add colored line in scroll_marker block. 578 | scroll_marker.append('
'); 579 | last_mark = $('#m' + line_number); 580 | last_mark.css({ 581 | 'height': line_height, 582 | 'top': line_top 583 | }); 584 | last_top = line_top; 585 | } 586 | 587 | previous_line = line_number; 588 | }); 589 | }; 590 | -------------------------------------------------------------------------------- /htmlcov/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Coverage report 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 28 |
29 | Hide keyboard shortcuts 30 |

Hot-keys on this page

31 |
32 |

33 | n 34 | s 35 | m 36 | x 37 | c   change column sorting 38 |

39 |
40 |
41 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 |
Modulestatementsmissingexcludedcoverage
Total1252098%
blog\__init__.py000100%
blog\admin.py700100%
blog\migrations\0001_initial.py800100%
blog\migrations\__init__.py000100%
blog\models.py2600100%
blog\tests.py2300100%
blog\urls.py400100%
blog_api\__init__.py000100%
blog_api\admin.py100100%
blog_api\migrations\__init__.py000100%
blog_api\models.py100100%
blog_api\serializers.py600100%
blog_api\tests.py100100%
blog_api\urls.py400100%
blog_api\views.py1000100%
core\__init__.py000100%
core\settings.py1900100%
core\urls.py300100%
manage.py122083%
197 |

198 | No items found using the specified filter. 199 |

200 |
201 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /htmlcov/jquery.ba-throttle-debounce.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery throttle / debounce - v1.1 - 3/7/2010 3 | * http://benalman.com/projects/jquery-throttle-debounce-plugin/ 4 | * 5 | * Copyright (c) 2010 "Cowboy" Ben Alman 6 | * Dual licensed under the MIT and GPL licenses. 7 | * http://benalman.com/about/license/ 8 | */ 9 | (function(b,c){var $=b.jQuery||b.Cowboy||(b.Cowboy={}),a;$.throttle=a=function(e,f,j,i){var h,d=0;if(typeof f!=="boolean"){i=j;j=f;f=c}function g(){var o=this,m=+new Date()-d,n=arguments;function l(){d=+new Date();j.apply(o,n)}function k(){h=c}if(i&&!h){l()}h&&clearTimeout(h);if(i===c&&m>e){l()}else{if(f!==true){h=setTimeout(i?k:l,i===c?e-m:e)}}}if($.guid){g.guid=j.guid=j.guid||$.guid++}return g};$.debounce=function(d,e,f){return f===c?a(d,e,false):a(d,f,e!==false)}})(this); 10 | -------------------------------------------------------------------------------- /htmlcov/jquery.hotkeys.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Hotkeys Plugin 3 | * Copyright 2010, John Resig 4 | * Dual licensed under the MIT or GPL Version 2 licenses. 5 | * 6 | * Based upon the plugin by Tzury Bar Yochay: 7 | * http://github.com/tzuryby/hotkeys 8 | * 9 | * Original idea by: 10 | * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ 11 | */ 12 | 13 | (function(jQuery){ 14 | 15 | jQuery.hotkeys = { 16 | version: "0.8", 17 | 18 | specialKeys: { 19 | 8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", 20 | 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", 21 | 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 22 | 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", 23 | 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", 24 | 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", 25 | 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta" 26 | }, 27 | 28 | shiftNums: { 29 | "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", 30 | "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", 31 | ".": ">", "/": "?", "\\": "|" 32 | } 33 | }; 34 | 35 | function keyHandler( handleObj ) { 36 | // Only care when a possible input has been specified 37 | if ( typeof handleObj.data !== "string" ) { 38 | return; 39 | } 40 | 41 | var origHandler = handleObj.handler, 42 | keys = handleObj.data.toLowerCase().split(" "); 43 | 44 | handleObj.handler = function( event ) { 45 | // Don't fire in text-accepting inputs that we didn't directly bind to 46 | if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) || 47 | event.target.type === "text") ) { 48 | return; 49 | } 50 | 51 | // Keypress represents characters, not special keys 52 | var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ], 53 | character = String.fromCharCode( event.which ).toLowerCase(), 54 | key, modif = "", possible = {}; 55 | 56 | // check combinations (alt|ctrl|shift+anything) 57 | if ( event.altKey && special !== "alt" ) { 58 | modif += "alt+"; 59 | } 60 | 61 | if ( event.ctrlKey && special !== "ctrl" ) { 62 | modif += "ctrl+"; 63 | } 64 | 65 | // TODO: Need to make sure this works consistently across platforms 66 | if ( event.metaKey && !event.ctrlKey && special !== "meta" ) { 67 | modif += "meta+"; 68 | } 69 | 70 | if ( event.shiftKey && special !== "shift" ) { 71 | modif += "shift+"; 72 | } 73 | 74 | if ( special ) { 75 | possible[ modif + special ] = true; 76 | 77 | } else { 78 | possible[ modif + character ] = true; 79 | possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true; 80 | 81 | // "$" can be triggered as "Shift+4" or "Shift+$" or just "$" 82 | if ( modif === "shift+" ) { 83 | possible[ jQuery.hotkeys.shiftNums[ character ] ] = true; 84 | } 85 | } 86 | 87 | for ( var i = 0, l = keys.length; i < l; i++ ) { 88 | if ( possible[ keys[i] ] ) { 89 | return origHandler.apply( this, arguments ); 90 | } 91 | } 92 | }; 93 | } 94 | 95 | jQuery.each([ "keydown", "keyup", "keypress" ], function() { 96 | jQuery.event.special[ this ] = { add: keyHandler }; 97 | }); 98 | 99 | })( jQuery ); 100 | -------------------------------------------------------------------------------- /htmlcov/jquery.isonscreen.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010 2 | * @author Laurence Wheway 3 | * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 4 | * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. 5 | * 6 | * @version 1.2.0 7 | */ 8 | (function($) { 9 | jQuery.extend({ 10 | isOnScreen: function(box, container) { 11 | //ensure numbers come in as intgers (not strings) and remove 'px' is it's there 12 | for(var i in box){box[i] = parseFloat(box[i])}; 13 | for(var i in container){container[i] = parseFloat(container[i])}; 14 | 15 | if(!container){ 16 | container = { 17 | left: $(window).scrollLeft(), 18 | top: $(window).scrollTop(), 19 | width: $(window).width(), 20 | height: $(window).height() 21 | } 22 | } 23 | 24 | if( box.left+box.width-container.left > 0 && 25 | box.left < container.width+container.left && 26 | box.top+box.height-container.top > 0 && 27 | box.top < container.height+container.top 28 | ) return true; 29 | return false; 30 | } 31 | }) 32 | 33 | 34 | jQuery.fn.isOnScreen = function (container) { 35 | for(var i in container){container[i] = parseFloat(container[i])}; 36 | 37 | if(!container){ 38 | container = { 39 | left: $(window).scrollLeft(), 40 | top: $(window).scrollTop(), 41 | width: $(window).width(), 42 | height: $(window).height() 43 | } 44 | } 45 | 46 | if( $(this).offset().left+$(this).width()-container.left > 0 && 47 | $(this).offset().left < container.width+container.left && 48 | $(this).offset().top+$(this).height()-container.top > 0 && 49 | $(this).offset().top < container.height+container.top 50 | ) return true; 51 | return false; 52 | } 53 | })(jQuery); 54 | -------------------------------------------------------------------------------- /htmlcov/jquery.tablesorter.min.js: -------------------------------------------------------------------------------- 1 | 2 | (function($){$.extend({tablesorter:new function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'.',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}var rows=table.tBodies[0].rows;if(table.tBodies[0].rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;ib)?1:0));};function sortTextDesc(a,b){return((ba)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){$this.trigger("sortStart");var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){var $cell=$(this);var i=this.column;this.order=this.count++%2;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i 2 | 3 | 4 | 5 | 6 | Coverage for manage.py: 83% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1#!/usr/bin/env python 

55 |

2"""Django's command-line utility for administrative tasks.""" 

56 |

3import os 

57 |

4import sys 

58 |

5 

59 |

6 

60 |

7def main(): 

61 |

8 """Run administrative tasks.""" 

62 |

9 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') 

63 |

10 try: 

64 |

11 from django.core.management import execute_from_command_line 

65 |

12 except ImportError as exc: 

66 |

13 raise ImportError( 

67 |

14 "Couldn't import Django. Are you sure it's installed and " 

68 |

15 "available on your PYTHONPATH environment variable? Did you " 

69 |

16 "forget to activate a virtual environment?" 

70 |

17 ) from exc 

71 |

18 execute_from_command_line(sys.argv) 

72 |

19 

73 |

20 

74 |

21if __name__ == '__main__': 

75 |

22 main() 

76 |
77 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /htmlcov/status.json: -------------------------------------------------------------------------------- 1 | {"format":2,"version":"5.2.1","globals":"c3fc29d529a3af98c1a2065daa2addf6","files":{"blog___init___py":{"hash":"af9d0d0de0ea3b71d198bc63f1499a7d","index":{"nums":[1,0,0,0,0,0,0],"html_filename":"blog___init___py.html","relative_filename":"blog\\__init__.py"}},"blog_admin_py":{"hash":"c4a66b1f102077f89e36517da546c95e","index":{"nums":[1,7,0,0,0,0,0],"html_filename":"blog_admin_py.html","relative_filename":"blog\\admin.py"}},"blog_migrations___init___py":{"hash":"af9d0d0de0ea3b71d198bc63f1499a7d","index":{"nums":[1,0,0,0,0,0,0],"html_filename":"blog_migrations___init___py.html","relative_filename":"blog\\migrations\\__init__.py"}},"blog_models_py":{"hash":"03ffa1ed7c73ebd42b64441405beea0a","index":{"nums":[1,26,0,0,0,0,0],"html_filename":"blog_models_py.html","relative_filename":"blog\\models.py"}},"blog_tests_py":{"hash":"e3d96d1c1c54be7e81a1b30a56bc2811","index":{"nums":[1,23,0,0,0,0,0],"html_filename":"blog_tests_py.html","relative_filename":"blog\\tests.py"}},"blog_urls_py":{"hash":"5de25e221f5f526ff48181ca0726b942","index":{"nums":[1,4,0,0,0,0,0],"html_filename":"blog_urls_py.html","relative_filename":"blog\\urls.py"}},"blog_api___init___py":{"hash":"af9d0d0de0ea3b71d198bc63f1499a7d","index":{"nums":[1,0,0,0,0,0,0],"html_filename":"blog_api___init___py.html","relative_filename":"blog_api\\__init__.py"}},"blog_api_admin_py":{"hash":"23b3f8ab894286afb6416200cf284c31","index":{"nums":[1,1,0,0,0,0,0],"html_filename":"blog_api_admin_py.html","relative_filename":"blog_api\\admin.py"}},"blog_api_migrations___init___py":{"hash":"af9d0d0de0ea3b71d198bc63f1499a7d","index":{"nums":[1,0,0,0,0,0,0],"html_filename":"blog_api_migrations___init___py.html","relative_filename":"blog_api\\migrations\\__init__.py"}},"blog_api_models_py":{"hash":"d1fe3410689f990be0aed25fad2be2c0","index":{"nums":[1,1,0,0,0,0,0],"html_filename":"blog_api_models_py.html","relative_filename":"blog_api\\models.py"}},"blog_api_serializers_py":{"hash":"62cc5cdad3c7e087b57c54036357802d","index":{"nums":[1,6,0,0,0,0,0],"html_filename":"blog_api_serializers_py.html","relative_filename":"blog_api\\serializers.py"}},"blog_api_tests_py":{"hash":"f19c76500f700766a5a7685bbf253a0f","index":{"nums":[1,1,0,0,0,0,0],"html_filename":"blog_api_tests_py.html","relative_filename":"blog_api\\tests.py"}},"blog_api_urls_py":{"hash":"d4a7c0b9d5213327f3d2e1817d5c0fae","index":{"nums":[1,4,0,0,0,0,0],"html_filename":"blog_api_urls_py.html","relative_filename":"blog_api\\urls.py"}},"blog_api_views_py":{"hash":"8111d2abe2a770e77f568afb8fbc8e79","index":{"nums":[1,10,0,0,0,0,0],"html_filename":"blog_api_views_py.html","relative_filename":"blog_api\\views.py"}},"core___init___py":{"hash":"af9d0d0de0ea3b71d198bc63f1499a7d","index":{"nums":[1,0,0,0,0,0,0],"html_filename":"core___init___py.html","relative_filename":"core\\__init__.py"}},"core_settings_py":{"hash":"33b3221df0cf35936e2298a8f67b8dd4","index":{"nums":[1,19,0,0,0,0,0],"html_filename":"core_settings_py.html","relative_filename":"core\\settings.py"}},"core_urls_py":{"hash":"23c5407846818bef9a012f5f36b91dfd","index":{"nums":[1,3,0,0,0,0,0],"html_filename":"core_urls_py.html","relative_filename":"core\\urls.py"}},"manage_py":{"hash":"f8c6d49629b8856f7f764a87376dfe41","index":{"nums":[1,12,0,2,0,0,0],"html_filename":"manage_py.html","relative_filename":"manage.py"}},"blog_migrations_0001_initial_py":{"hash":"d628aa3eab1ab439a0f86f40d58265f6","index":{"nums":[1,8,0,0,0,0,0],"html_filename":"blog_migrations_0001_initial_py.html","relative_filename":"blog\\migrations\\0001_initial.py"}}}} -------------------------------------------------------------------------------- /htmlcov/style.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ 3 | /* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ 4 | /* Don't edit this .css file. Edit the .scss file instead! */ 5 | html, body, h1, h2, h3, p, table, td, th { margin: 0; padding: 0; border: 0; font-weight: inherit; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline; } 6 | 7 | body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-size: 1em; background: #fff; color: #000; } 8 | 9 | @media (prefers-color-scheme: dark) { body { background: #1e1e1e; } } 10 | 11 | @media (prefers-color-scheme: dark) { body { color: #eee; } } 12 | 13 | html > body { font-size: 16px; } 14 | 15 | a:active, a:focus { outline: 2px dashed #007acc; } 16 | 17 | p { font-size: .875em; line-height: 1.4em; } 18 | 19 | table { border-collapse: collapse; } 20 | 21 | td { vertical-align: top; } 22 | 23 | table tr.hidden { display: none !important; } 24 | 25 | p#no_rows { display: none; font-size: 1.2em; } 26 | 27 | a.nav { text-decoration: none; color: inherit; } 28 | 29 | a.nav:hover { text-decoration: underline; color: inherit; } 30 | 31 | #header { background: #f8f8f8; width: 100%; border-bottom: 1px solid #eee; } 32 | 33 | @media (prefers-color-scheme: dark) { #header { background: black; } } 34 | 35 | @media (prefers-color-scheme: dark) { #header { border-color: #333; } } 36 | 37 | .indexfile #footer { margin: 1rem 3rem; } 38 | 39 | .pyfile #footer { margin: 1rem 1rem; } 40 | 41 | #footer .content { padding: 0; color: #666; font-style: italic; } 42 | 43 | @media (prefers-color-scheme: dark) { #footer .content { color: #aaa; } } 44 | 45 | #index { margin: 1rem 0 0 3rem; } 46 | 47 | #header .content { padding: 1rem 3rem; } 48 | 49 | h1 { font-size: 1.25em; display: inline-block; } 50 | 51 | #filter_container { float: right; margin: 0 2em 0 0; } 52 | 53 | #filter_container input { width: 10em; padding: 0.2em 0.5em; border: 2px solid #ccc; background: #fff; color: #000; } 54 | 55 | @media (prefers-color-scheme: dark) { #filter_container input { border-color: #444; } } 56 | 57 | @media (prefers-color-scheme: dark) { #filter_container input { background: #1e1e1e; } } 58 | 59 | @media (prefers-color-scheme: dark) { #filter_container input { color: #eee; } } 60 | 61 | #filter_container input:focus { border-color: #007acc; } 62 | 63 | h2.stats { margin-top: .5em; font-size: 1em; } 64 | 65 | .stats button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; color: inherit; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; } 66 | 67 | @media (prefers-color-scheme: dark) { .stats button { border-color: #444; } } 68 | 69 | .stats button:active, .stats button:focus { outline: 2px dashed #007acc; } 70 | 71 | .stats button:active, .stats button:focus { outline: 2px dashed #007acc; } 72 | 73 | .stats button.run { background: #eeffee; } 74 | 75 | @media (prefers-color-scheme: dark) { .stats button.run { background: #373d29; } } 76 | 77 | .stats button.run.show_run { background: #dfd; border: 2px solid #00dd00; margin: 0 .1em; } 78 | 79 | @media (prefers-color-scheme: dark) { .stats button.run.show_run { background: #373d29; } } 80 | 81 | .stats button.mis { background: #ffeeee; } 82 | 83 | @media (prefers-color-scheme: dark) { .stats button.mis { background: #4b1818; } } 84 | 85 | .stats button.mis.show_mis { background: #fdd; border: 2px solid #ff0000; margin: 0 .1em; } 86 | 87 | @media (prefers-color-scheme: dark) { .stats button.mis.show_mis { background: #4b1818; } } 88 | 89 | .stats button.exc { background: #f7f7f7; } 90 | 91 | @media (prefers-color-scheme: dark) { .stats button.exc { background: #333; } } 92 | 93 | .stats button.exc.show_exc { background: #eee; border: 2px solid #808080; margin: 0 .1em; } 94 | 95 | @media (prefers-color-scheme: dark) { .stats button.exc.show_exc { background: #333; } } 96 | 97 | .stats button.par { background: #ffffd5; } 98 | 99 | @media (prefers-color-scheme: dark) { .stats button.par { background: #650; } } 100 | 101 | .stats button.par.show_par { background: #ffa; border: 2px solid #dddd00; margin: 0 .1em; } 102 | 103 | @media (prefers-color-scheme: dark) { .stats button.par.show_par { background: #650; } } 104 | 105 | .help_panel, #source p .annotate.long { display: none; position: absolute; z-index: 999; background: #ffffcc; border: 1px solid #888; border-radius: .2em; color: #333; padding: .25em .5em; } 106 | 107 | #source p .annotate.long { white-space: normal; float: right; top: 1.75em; right: 1em; height: auto; } 108 | 109 | #keyboard_icon { float: right; margin: 5px; cursor: pointer; } 110 | 111 | .help_panel { padding: .5em; border: 1px solid #883; } 112 | 113 | .help_panel .legend { font-style: italic; margin-bottom: 1em; } 114 | 115 | .indexfile .help_panel { width: 20em; min-height: 4em; } 116 | 117 | .pyfile .help_panel { width: 16em; min-height: 8em; } 118 | 119 | #panel_icon { float: right; cursor: pointer; } 120 | 121 | .keyhelp { margin: .75em; } 122 | 123 | .keyhelp .key { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em .35em; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-weight: bold; background: #eee; } 124 | 125 | #source { padding: 1em 0 1em 3rem; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; } 126 | 127 | #source p { position: relative; white-space: pre; } 128 | 129 | #source p * { box-sizing: border-box; } 130 | 131 | #source p .n { float: left; text-align: right; width: 3rem; box-sizing: border-box; margin-left: -3rem; padding-right: 1em; color: #999; } 132 | 133 | @media (prefers-color-scheme: dark) { #source p .n { color: #777; } } 134 | 135 | #source p .n a { text-decoration: none; color: #999; } 136 | 137 | @media (prefers-color-scheme: dark) { #source p .n a { color: #777; } } 138 | 139 | #source p .n a:hover { text-decoration: underline; color: #999; } 140 | 141 | @media (prefers-color-scheme: dark) { #source p .n a:hover { color: #777; } } 142 | 143 | #source p.highlight .n { background: #ffdd00; } 144 | 145 | #source p .t { display: inline-block; width: 100%; box-sizing: border-box; margin-left: -.5em; padding-left: 0.3em; border-left: 0.2em solid #fff; } 146 | 147 | @media (prefers-color-scheme: dark) { #source p .t { border-color: #1e1e1e; } } 148 | 149 | #source p .t:hover { background: #f2f2f2; } 150 | 151 | @media (prefers-color-scheme: dark) { #source p .t:hover { background: #282828; } } 152 | 153 | #source p .t:hover ~ .r .annotate.long { display: block; } 154 | 155 | #source p .t .com { color: #008000; font-style: italic; line-height: 1px; } 156 | 157 | @media (prefers-color-scheme: dark) { #source p .t .com { color: #6A9955; } } 158 | 159 | #source p .t .key { font-weight: bold; line-height: 1px; } 160 | 161 | #source p .t .str { color: #0451A5; } 162 | 163 | @media (prefers-color-scheme: dark) { #source p .t .str { color: #9CDCFE; } } 164 | 165 | #source p.mis .t { border-left: 0.2em solid #ff0000; } 166 | 167 | #source p.mis.show_mis .t { background: #fdd; } 168 | 169 | @media (prefers-color-scheme: dark) { #source p.mis.show_mis .t { background: #4b1818; } } 170 | 171 | #source p.mis.show_mis .t:hover { background: #f2d2d2; } 172 | 173 | @media (prefers-color-scheme: dark) { #source p.mis.show_mis .t:hover { background: #532323; } } 174 | 175 | #source p.run .t { border-left: 0.2em solid #00dd00; } 176 | 177 | #source p.run.show_run .t { background: #dfd; } 178 | 179 | @media (prefers-color-scheme: dark) { #source p.run.show_run .t { background: #373d29; } } 180 | 181 | #source p.run.show_run .t:hover { background: #d2f2d2; } 182 | 183 | @media (prefers-color-scheme: dark) { #source p.run.show_run .t:hover { background: #404633; } } 184 | 185 | #source p.exc .t { border-left: 0.2em solid #808080; } 186 | 187 | #source p.exc.show_exc .t { background: #eee; } 188 | 189 | @media (prefers-color-scheme: dark) { #source p.exc.show_exc .t { background: #333; } } 190 | 191 | #source p.exc.show_exc .t:hover { background: #e2e2e2; } 192 | 193 | @media (prefers-color-scheme: dark) { #source p.exc.show_exc .t:hover { background: #3c3c3c; } } 194 | 195 | #source p.par .t { border-left: 0.2em solid #dddd00; } 196 | 197 | #source p.par.show_par .t { background: #ffa; } 198 | 199 | @media (prefers-color-scheme: dark) { #source p.par.show_par .t { background: #650; } } 200 | 201 | #source p.par.show_par .t:hover { background: #f2f2a2; } 202 | 203 | @media (prefers-color-scheme: dark) { #source p.par.show_par .t:hover { background: #6d5d0c; } } 204 | 205 | #source p .r { position: absolute; top: 0; right: 2.5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } 206 | 207 | #source p .annotate { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; color: #666; padding-right: .5em; } 208 | 209 | @media (prefers-color-scheme: dark) { #source p .annotate { color: #ddd; } } 210 | 211 | #source p .annotate.short:hover ~ .long { display: block; } 212 | 213 | #source p .annotate.long { width: 30em; right: 2.5em; } 214 | 215 | #source p input { display: none; } 216 | 217 | #source p input ~ .r label.ctx { cursor: pointer; border-radius: .25em; } 218 | 219 | #source p input ~ .r label.ctx::before { content: "▶ "; } 220 | 221 | #source p input ~ .r label.ctx:hover { background: #d5f7ff; color: #666; } 222 | 223 | @media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { background: #0f3a42; } } 224 | 225 | @media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { color: #aaa; } } 226 | 227 | #source p input:checked ~ .r label.ctx { background: #aef; color: #666; border-radius: .75em .75em 0 0; padding: 0 .5em; margin: -.25em 0; } 228 | 229 | @media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { background: #056; } } 230 | 231 | @media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { color: #aaa; } } 232 | 233 | #source p input:checked ~ .r label.ctx::before { content: "▼ "; } 234 | 235 | #source p input:checked ~ .ctxs { padding: .25em .5em; overflow-y: scroll; max-height: 10.5em; } 236 | 237 | #source p label.ctx { color: #999; display: inline-block; padding: 0 .5em; font-size: .8333em; } 238 | 239 | @media (prefers-color-scheme: dark) { #source p label.ctx { color: #777; } } 240 | 241 | #source p .ctxs { display: block; max-height: 0; overflow-y: hidden; transition: all .2s; padding: 0 .5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; white-space: nowrap; background: #aef; border-radius: .25em; margin-right: 1.75em; } 242 | 243 | @media (prefers-color-scheme: dark) { #source p .ctxs { background: #056; } } 244 | 245 | #source p .ctxs span { display: block; text-align: right; } 246 | 247 | #index { font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.875em; } 248 | 249 | #index table.index { margin-left: -.5em; } 250 | 251 | #index td, #index th { text-align: right; width: 5em; padding: .25em .5em; border-bottom: 1px solid #eee; } 252 | 253 | @media (prefers-color-scheme: dark) { #index td, #index th { border-color: #333; } } 254 | 255 | #index td.name, #index th.name { text-align: left; width: auto; } 256 | 257 | #index th { font-style: italic; color: #333; cursor: pointer; } 258 | 259 | @media (prefers-color-scheme: dark) { #index th { color: #ddd; } } 260 | 261 | #index th:hover { background: #eee; } 262 | 263 | @media (prefers-color-scheme: dark) { #index th:hover { background: #333; } } 264 | 265 | #index th.headerSortDown, #index th.headerSortUp { white-space: nowrap; background: #eee; } 266 | 267 | @media (prefers-color-scheme: dark) { #index th.headerSortDown, #index th.headerSortUp { background: #333; } } 268 | 269 | #index th.headerSortDown:after { content: " ↑"; } 270 | 271 | #index th.headerSortUp:after { content: " ↓"; } 272 | 273 | #index td.name a { text-decoration: none; color: inherit; } 274 | 275 | #index tr.total td, #index tr.total_dynamic td { font-weight: bold; border-top: 1px solid #ccc; border-bottom: none; } 276 | 277 | #index tr.file:hover { background: #eee; } 278 | 279 | @media (prefers-color-scheme: dark) { #index tr.file:hover { background: #333; } } 280 | 281 | #index tr.file:hover td.name { text-decoration: underline; color: inherit; } 282 | 283 | #scroll_marker { position: fixed; right: 0; top: 0; width: 16px; height: 100%; background: #fff; border-left: 1px solid #eee; will-change: transform; } 284 | 285 | @media (prefers-color-scheme: dark) { #scroll_marker { background: #1e1e1e; } } 286 | 287 | @media (prefers-color-scheme: dark) { #scroll_marker { border-color: #333; } } 288 | 289 | #scroll_marker .marker { background: #ccc; position: absolute; min-height: 3px; width: 100%; } 290 | 291 | @media (prefers-color-scheme: dark) { #scroll_marker .marker { background: #444; } } 292 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /templates/blog/index.html: -------------------------------------------------------------------------------- 1 | //index --------------------------------------------------------------------------------